diff --git a/packages/features/insights/filters/UsersListInTeam.tsx b/packages/features/insights/filters/UsersListInTeam.tsx
index 9994e10607..2eefb0d775 100644
--- a/packages/features/insights/filters/UsersListInTeam.tsx
+++ b/packages/features/insights/filters/UsersListInTeam.tsx
@@ -2,7 +2,7 @@ import {
FilterCheckboxField,
FilterCheckboxFieldsContainer,
} from "@calcom/features/filters/components/TeamsFilter";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc";
import { trpc } from "@calcom/trpc";
@@ -21,6 +21,7 @@ const mapUserToOption = (user: User): Option => ({
export const UserListInTeam = () => {
const { t } = useLocale();
+ const bookerUrl = useBookerUrl();
const { filter, setConfigFilters } = useFilterContext();
const { selectedFilter, selectedTeamId, selectedMemberUserId, isAll } = filter;
const { data, isSuccess } = trpc.viewer.insights.userList.useQuery({
@@ -67,7 +68,7 @@ export const UserListInTeam = () => {
icon={
}
diff --git a/packages/features/webhooks/pages/webhooks-view.tsx b/packages/features/webhooks/pages/webhooks-view.tsx
index a74125bfe4..ce72459e8f 100644
--- a/packages/features/webhooks/pages/webhooks-view.tsx
+++ b/packages/features/webhooks/pages/webhooks-view.tsx
@@ -2,6 +2,7 @@ import { useRouter } from "next/router";
import { Suspense } from "react";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
+import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { WebhooksByViewer } from "@calcom/trpc/server/routers/viewer/webhook/getByViewer.handler";
@@ -54,6 +55,7 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
const router = useRouter();
const { profiles, webhookGroups } = webhooksByViewer;
+ const bookerUrl = useBookerUrl();
const hasTeams = profiles && profiles.length > 1;
@@ -69,7 +71,7 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts
index fe1638afab..efff6f5566 100644
--- a/packages/lib/CalEventParser.ts
+++ b/packages/lib/CalEventParser.ts
@@ -157,12 +157,12 @@ const getSeatReferenceId = (calEvent: CalendarEvent): string => {
export const getManageLink = (calEvent: CalendarEvent, t: TFunction) => {
return `
${t("need_to_reschedule_or_cancel")}
-${WEBAPP_URL + "/booking/" + getUid(calEvent) + "?changes=true"}
+${(calEvent.bookerUrl ?? WEBAPP_URL) + "/booking/" + getUid(calEvent) + "?changes=true"}
`;
};
export const getCancelLink = (calEvent: CalendarEvent): string => {
- const cancelLink = new URL(WEBAPP_URL + `/booking/${getUid(calEvent)}`);
+ const cancelLink = new URL((calEvent.bookerUrl ?? WEBAPP_URL) + `/booking/${getUid(calEvent)}`);
cancelLink.searchParams.append("cancel", "true");
cancelLink.searchParams.append("allRemainingBookings", String(!!calEvent.recurringEvent));
const seatReferenceUid = getSeatReferenceId(calEvent);
@@ -174,7 +174,7 @@ export const getRescheduleLink = (calEvent: CalendarEvent): string => {
const Uid = getUid(calEvent);
const seatUid = getSeatReferenceId(calEvent);
- return `${WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`;
+ return `${calEvent.bookerUrl ?? WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`;
};
export const getRichDescription = (calEvent: CalendarEvent, t_?: TFunction /*, attendee?: Person*/) => {
diff --git a/packages/lib/hooks/useBookerUrl.ts b/packages/lib/hooks/useBookerUrl.ts
new file mode 100644
index 0000000000..37e0253cec
--- /dev/null
+++ b/packages/lib/hooks/useBookerUrl.ts
@@ -0,0 +1,7 @@
+import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
+import { WEBAPP_URL } from "@calcom/lib/constants";
+
+export const useBookerUrl = () => {
+ const orgBranding = useOrgBrandingValues();
+ return orgBranding?.fullDomain ?? WEBAPP_URL;
+};
diff --git a/packages/lib/server/getBookerUrl.ts b/packages/lib/server/getBookerUrl.ts
new file mode 100644
index 0000000000..9b42c92a30
--- /dev/null
+++ b/packages/lib/server/getBookerUrl.ts
@@ -0,0 +1,7 @@
+import { WEBAPP_URL } from "../constants";
+import { getBrand } from "./getBrand";
+
+export const getBookerUrl = async (user: { organizationId: number | null }) => {
+ const orgBrand = await getBrand(user.organizationId);
+ return orgBrand?.fullDomain ?? WEBAPP_URL;
+};
diff --git a/packages/lib/server/getBrand.ts b/packages/lib/server/getBrand.ts
new file mode 100644
index 0000000000..98ac5d4b3e
--- /dev/null
+++ b/packages/lib/server/getBrand.ts
@@ -0,0 +1,32 @@
+import { subdomainSuffix, getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
+import { prisma } from "@calcom/prisma";
+import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
+
+export const getBrand = async (orgId: number | null) => {
+ if (!orgId) {
+ return null;
+ }
+ const org = await prisma.team.findFirst({
+ where: {
+ id: orgId,
+ },
+ select: {
+ logo: true,
+ name: true,
+ slug: true,
+ metadata: true,
+ },
+ });
+ const metadata = teamMetadataSchema.parse(org?.metadata);
+ const slug = (org?.slug || metadata?.requestedSlug) as string;
+ const fullDomain = getOrgFullDomain(slug);
+ const domainSuffix = subdomainSuffix();
+
+ return {
+ ...org,
+ metadata,
+ slug,
+ fullDomain,
+ domainSuffix,
+ };
+};
diff --git a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts
index 4f82991565..64d2aed7e6 100644
--- a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts
+++ b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts
@@ -1,7 +1,7 @@
import { type PrismaClient } from "@prisma/client";
-import { CAL_URL } from "@calcom/lib/constants";
import { isOrganization, withRoleCanCreateEntity } from "@calcom/lib/entityPermissionUtils";
+import { getBookerUrl } from "@calcom/lib/server/getBookerUrl";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { TRPCError } from "@trpc/server";
@@ -46,12 +46,15 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti
},
},
},
+ organizationId: true,
},
});
if (!user) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
- const image = user?.username ? `${CAL_URL}/${user.username}/avatar.png` : undefined;
+ const bookerUrl = await getBookerUrl(user);
+
+ const image = user?.username ? `${bookerUrl}/${user.username}/avatar.png` : undefined;
const nonOrgTeams = user.teams.filter((membership) => !isOrganization({ team: membership.team }));
return [
@@ -66,7 +69,7 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti
teamId: membership.team.id,
name: membership.team.name,
slug: membership.team.slug ? "team/" + membership.team.slug : null,
- image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
+ image: `${bookerUrl}${membership.team.slug ? "/team" : ""}/${membership.team.slug}/avatar.png`,
role: membership.role,
readOnly: !withRoleCanCreateEntity(membership.role),
})),
diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
index 86204bd6f7..aebf392d57 100644
--- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
+++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
@@ -4,6 +4,7 @@ import { orderBy } from "lodash";
import { hasFilter } from "@calcom/features/filters/lib/hasFilter";
import { CAL_URL } from "@calcom/lib/constants";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
+import { getBookerUrl } from "@calcom/lib/server/getBookerUrl";
import { baseEventTypeSelect } from "@calcom/prisma";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
@@ -89,6 +90,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
endTime: true,
bufferTime: true,
avatar: true,
+ organizationId: true,
teams: {
where: {
accepted: true,
@@ -276,6 +278,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
})
);
+ const bookerUrl = await getBookerUrl(user);
return {
// don't display event teams without event types,
eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length),
@@ -285,7 +288,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
...group.metadata,
teamId: group.teamId,
membershipRole: group.membershipRole,
- image: `${CAL_URL}${group.teamId ? "/team" : ""}/${group.profile.slug}/avatar.png`,
+ image: `${bookerUrl}${group.teamId ? "/team" : ""}/${group.profile.slug}/avatar.png`,
})),
};
};
diff --git a/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts b/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
index 2cb920857a..1ce33ca173 100644
--- a/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
@@ -1,6 +1,4 @@
-import { subdomainSuffix, getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
-import { prisma } from "@calcom/prisma";
-import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
+import { getBrand } from "@calcom/lib/server/getBrand";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
type VerifyCodeOptions = {
@@ -14,28 +12,5 @@ export const getBrandHandler = async ({ ctx }: VerifyCodeOptions) => {
if (!user.organizationId) return null;
- const team = await prisma.team.findFirst({
- where: {
- id: user.organizationId,
- },
- select: {
- logo: true,
- name: true,
- slug: true,
- metadata: true,
- },
- });
-
- const metadata = teamMetadataSchema.parse(team?.metadata);
- const slug = (team?.slug || metadata?.requestedSlug) as string;
- const fullDomain = getOrgFullDomain(slug);
- const domainSuffix = subdomainSuffix();
-
- return {
- ...team,
- metadata,
- slug,
- fullDomain,
- domainSuffix,
- };
+ return await getBrand(user.organizationId);
};
diff --git a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
index b0b33e7fe4..a865fab1bd 100644
--- a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
+++ b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
@@ -1,4 +1,4 @@
-import { CAL_URL } from "@calcom/lib/constants";
+import { getBookerUrl } from "@calcom/lib/server/getBookerUrl";
import { prisma } from "@calcom/prisma";
import type { Webhook } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
@@ -49,6 +49,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
avatar: true,
name: true,
webhooks: true,
+ organizationId: true,
teams: {
where: {
accepted: true,
@@ -81,8 +82,9 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
const userWebhooks = user.webhooks;
let webhookGroups: WebhookGroup[] = [];
+ const bookerUrl = await getBookerUrl(user);
- const image = user?.username ? `${CAL_URL}/${user.username}/avatar.png` : undefined;
+ const image = user?.username ? `${bookerUrl}/${user.username}/avatar.png` : undefined;
webhookGroups.push({
teamId: null,
profile: {
@@ -119,7 +121,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
? `/team`
: "" + membership.team.slug
: null,
- image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
+ image: `${bookerUrl}/team/${membership.team.slug}/avatar.png`,
},
metadata: {
readOnly:
@@ -142,7 +144,6 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
teamId: group.teamId,
...group.profile,
...group.metadata,
- image: `${CAL_URL}/${group.profile.slug}/avatar.png`,
})),
};
};
diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts
index 52deb0f43a..fe1f742c3a 100644
--- a/packages/types/Calendar.d.ts
+++ b/packages/types/Calendar.d.ts
@@ -146,6 +146,9 @@ export type CalEventResponses = Record<
// If modifying this interface, probably should update builders/calendarEvent files
export interface CalendarEvent {
+ // Instead of sending this per event.
+ // TODO: Links sent in email should be validated and automatically redirected to org domain or regular app. It would be a much cleaner way. Maybe use existing /api/link endpoint
+ bookerUrl?: string;
type: string;
title: string;
startTime: string;
diff --git a/packages/ui/components/createButton/CreateButton.tsx b/packages/ui/components/createButton/CreateButton.tsx
index 1a68ee397f..1b4e9bebc1 100644
--- a/packages/ui/components/createButton/CreateButton.tsx
+++ b/packages/ui/components/createButton/CreateButton.tsx
@@ -1,6 +1,6 @@
import { useRouter } from "next/router";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Avatar,
@@ -38,6 +38,8 @@ export type CreateBtnProps = {
export function CreateButton(props: CreateBtnProps) {
const { t } = useLocale();
const router = useRouter();
+ const bookerUrl = useBookerUrl();
+
const {
createDialog,
options,
@@ -113,7 +115,7 @@ export function CreateButton(props: CreateBtnProps) {
StartIcon={(props) => (