@@ -233,6 +241,22 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
let username = guessUsernameFromEmail(verificationToken.identifier);
+ const orgInfo = await prisma.user.findFirst({
+ where: {
+ email: verificationToken?.identifier,
+ },
+ select: {
+ organization: {
+ select: {
+ slug: true,
+ metadata: true,
+ },
+ },
+ },
+ });
+
+ const userOrgMetadata = teamMetadataSchema.parse(orgInfo?.organization?.metadata ?? {});
+
if (!IS_SELF_HOSTED) {
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
const { available, suggestion } = await checkPremiumUsername(username);
@@ -248,6 +272,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
email: verificationToken.identifier,
username,
},
+ orgSlug: (orgInfo?.organization?.slug || userOrgMetadata?.requestedSlug) ?? null,
},
};
};
diff --git a/packages/features/ee/organizations/components/AboutOrganizationForm.tsx b/packages/features/ee/organizations/components/AboutOrganizationForm.tsx
index 1ba3142edc..24ee3218b3 100644
--- a/packages/features/ee/organizations/components/AboutOrganizationForm.tsx
+++ b/packages/features/ee/organizations/components/AboutOrganizationForm.tsx
@@ -65,6 +65,7 @@ export const AboutOrganizationForm = () => {
fallback={
}
asChild
className="items-center"
+ imageSrc={image}
size="lg"
/>
diff --git a/packages/features/ee/organizations/context/provider.ts b/packages/features/ee/organizations/context/provider.ts
index 1162186d6e..e32e001611 100644
--- a/packages/features/ee/organizations/context/provider.ts
+++ b/packages/features/ee/organizations/context/provider.ts
@@ -12,7 +12,9 @@ export type OrganizationBranding =
| ({
logo?: string | null | undefined;
name?: string;
- slug?: string;
+ slug: string;
+ fullDomain: string;
+ domainSuffix: string;
} & z.infer)
| null
| undefined;
diff --git a/packages/features/ee/organizations/lib/orgDomains.ts b/packages/features/ee/organizations/lib/orgDomains.ts
index 141719b2cd..2c8da147de 100644
--- a/packages/features/ee/organizations/lib/orgDomains.ts
+++ b/packages/features/ee/organizations/lib/orgDomains.ts
@@ -4,7 +4,7 @@ import { ALLOWED_HOSTNAMES, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/
* return the org slug
* @param hostname
*/
-export function getOrgDomain(hostname: string) {
+export function getOrgSlug(hostname: string) {
// Find which hostname is being currently used
const currentHostname = ALLOWED_HOSTNAMES.find((ahn) => {
const url = new URL(WEBAPP_URL);
@@ -20,7 +20,7 @@ export function getOrgDomain(hostname: string) {
}
export function orgDomainConfig(hostname: string) {
- const currentOrgDomain = getOrgDomain(hostname);
+ const currentOrgDomain = getOrgSlug(hostname);
return {
currentOrgDomain,
isValidOrgDomain: currentOrgDomain !== null && !RESERVED_SUBDOMAINS.includes(currentOrgDomain),
@@ -31,3 +31,7 @@ export function subdomainSuffix() {
const urlSplit = WEBAPP_URL.replace("https://", "")?.replace("http://", "").split(".");
return urlSplit.length === 3 ? urlSplit.slice(1).join(".") : urlSplit.join(".");
}
+
+export function getOrgFullDomain(slug: string, options: { protocol: boolean } = { protocol: true }) {
+ return `${options.protocol ? `${new URL(WEBAPP_URL).protocol}//` : ""}${slug}.${subdomainSuffix()}/`;
+}
diff --git a/packages/features/ee/organizations/pages/settings/appearance.tsx b/packages/features/ee/organizations/pages/settings/appearance.tsx
index 3b9137d623..d447bc77f8 100644
--- a/packages/features/ee/organizations/pages/settings/appearance.tsx
+++ b/packages/features/ee/organizations/pages/settings/appearance.tsx
@@ -1,7 +1,6 @@
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
-import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
@@ -14,7 +13,6 @@ import {
SkeletonButton,
SkeletonContainer,
SkeletonText,
- Switch,
} from "@calcom/ui";
import ThemeLabel from "../../../../settings/ThemeLabel";
@@ -170,59 +168,6 @@ const OrgAppearanceView = () => {
)}
/>
-
-
-
-
-
-
-
- {t("team_disable_cal_branding_description", { appName: APP_NAME })}
-
-
-
-
- (
- {
- form.setValue("hideBranding", isChecked);
- }}
- />
- )}
- />
-
-
-
-
-
-
{t("hide_book_a_team_member_description")}
-
-
- (
- {
- form.setValue("hideBookATeamMember", isChecked);
- }}
- />
- )}
- />
-
-
-
diff --git a/packages/features/ee/organizations/pages/settings/profile.tsx b/packages/features/ee/organizations/pages/settings/profile.tsx
index e12d6657fa..8640a81c85 100644
--- a/packages/features/ee/organizations/pages/settings/profile.tsx
+++ b/packages/features/ee/organizations/pages/settings/profile.tsx
@@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import type { Prisma } from "@prisma/client";
import { LinkIcon, Trash2 } from "lucide-react";
-import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState, useLayoutEffect } from "react";
import { Controller, useForm } from "react-hook-form";
@@ -34,8 +33,6 @@ import {
import { getLayout } from "../../../../settings/layouts/SettingsLayout";
-const regex = new RegExp("^[a-zA-Z0-9-]*$");
-
const orgProfileFormSchema = z.object({
name: z.string(),
logo: z.string(),
@@ -46,7 +43,6 @@ const OrgProfileView = () => {
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
- const session = useSession();
const [firstRender, setFirstRender] = useState(true);
const orgBranding = useOrgBrandingValues();
@@ -54,6 +50,10 @@ const OrgProfileView = () => {
document.body.focus();
}, []);
+ const form = useForm({
+ resolver: zodResolver(orgProfileFormSchema),
+ });
+
const mutation = trpc.viewer.organizations.update.useMutation({
onError: (err) => {
showToast(err.message, "error");
@@ -64,10 +64,6 @@ const OrgProfileView = () => {
},
});
- const form = useForm({
- resolver: zodResolver(orgProfileFormSchema),
- });
-
const { data: currentOrganisation, isLoading } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
onError: () => {
router.push("/settings");
@@ -90,10 +86,6 @@ const OrgProfileView = () => {
(currentOrganisation.user.role === MembershipRole.OWNER ||
currentOrganisation.user.role === MembershipRole.ADMIN);
- const permalink = `${new URL(process.env.NEXT_PUBLIC_WEBSITE_URL || "").protocol}//${
- orgBranding?.slug
- }.${subdomainSuffix()}`;
-
const isBioEmpty =
!currentOrganisation ||
!currentOrganisation.bio ||
@@ -107,6 +99,8 @@ const OrgProfileView = () => {
},
});
+ if (!orgBranding) return null;
+
function deleteTeam() {
if (currentOrganisation?.id) deleteTeamMutation.mutate({ teamId: currentOrganisation.id });
}
@@ -222,7 +216,7 @@ const OrgProfileView = () => {
{
- navigator.clipboard.writeText(permalink);
+ navigator.clipboard.writeText(orgBranding.fullDomain);
showToast("Copied to clipboard", "success");
}}>
{t("copy_link_org")}
diff --git a/packages/features/ee/teams/components/CreateANewTeamForm.tsx b/packages/features/ee/teams/components/CreateANewTeamForm.tsx
index b5f9fc03b3..7eaaf56860 100644
--- a/packages/features/ee/teams/components/CreateANewTeamForm.tsx
+++ b/packages/features/ee/teams/components/CreateANewTeamForm.tsx
@@ -4,13 +4,14 @@ import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { extractDomainFromWebsiteUrl } from "@calcom/ee/organizations/lib/utils";
+import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import slugify from "@calcom/lib/slugify";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
-import { Avatar, Button, Form, ImageUploader, TextField, Alert } from "@calcom/ui";
-import { ArrowRight } from "@calcom/ui/components/icon";
+import { Avatar, Button, Form, ImageUploader, TextField, Alert, Label } from "@calcom/ui";
+import { ArrowRight, Plus } from "@calcom/ui/components/icon";
import type { NewTeamFormValues } from "../lib/types";
@@ -26,6 +27,7 @@ export const CreateANewTeamForm = () => {
const parsedQuery = querySchema.safeParse(router.query);
console.log({ parsedQuery });
const [serverErrorMessage, setServerErrorMessage] = useState(null);
+ const orgBranding = useOrgBrandingValues();
const returnToParam =
(parsedQuery.success ? getSafeRedirectUrl(parsedQuery.data.returnTo) : "/settings/teams") ||
@@ -107,7 +109,10 @@ export const CreateANewTeamForm = () => {
name="slug"
placeholder="acme"
label={t("team_url")}
- addOnLeading={`${extractDomainFromWebsiteUrl}/team/`}
+ addOnLeading={`${
+ orgBranding?.fullDomain.replace("https://", "").replace("http://", "") ??
+ `${extractDomainFromWebsiteUrl}/team/`
+ }`}
defaultValue={value}
onChange={(e) => {
newTeamFormMethods.setValue("slug", slugify(e?.target.value), {
@@ -125,21 +130,29 @@ export const CreateANewTeamForm = () => {
control={newTeamFormMethods.control}
name="logo"
render={({ field: { value } }) => (
-
-
-
-
{
- newTeamFormMethods.setValue("logo", newAvatar);
- createTeamMutation.reset();
- }}
+ <>
+
+
+
}
+ size="lg"
/>
+
+ {
+ newTeamFormMethods.setValue("logo", newAvatar);
+ createTeamMutation.reset();
+ }}
+ imageSrc={value}
+ />
+
-
+ >
)}
/>
diff --git a/packages/features/ee/teams/components/TeamListItem.tsx b/packages/features/ee/teams/components/TeamListItem.tsx
index 281b3484c5..f40d6a3885 100644
--- a/packages/features/ee/teams/components/TeamListItem.tsx
+++ b/packages/features/ee/teams/components/TeamListItem.tsx
@@ -5,7 +5,6 @@ import { useState } from "react";
import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSettingsModal";
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
-import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -104,7 +103,7 @@ export default function TeamListItem(props: Props) {
{team.slug
? orgBranding
- ? `${orgBranding.slug}.${subdomainSuffix()}/${team.slug}`
+ ? `${orgBranding.fullDomain}${team.slug}`
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/${team.slug}`
: "Unpublished team"}
@@ -235,7 +234,11 @@ export default function TeamListItem(props: Props) {
color="secondary"
onClick={() => {
navigator.clipboard.writeText(
- process.env.NEXT_PUBLIC_WEBSITE_URL + "/team/" + team.slug
+ `${
+ orgBranding
+ ? `${orgBranding.fullDomain}`
+ : process.env.NEXT_PUBLIC_WEBSITE_URL + "/team/"
+ }${team.slug}`
);
showToast(t("link_copied"), "success");
}}
@@ -271,7 +274,11 @@ export default function TeamListItem(props: Props) {
{t("preview_team") as string}
diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
index 0877a82bf4..f03109f78b 100644
--- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
+++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
@@ -7,7 +7,6 @@ import { useForm } from "react-hook-form";
import { z } from "zod";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
-import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -139,9 +138,7 @@ export default function CreateEventTypeDialog({
});
const flags = useFlagMap();
- const urlPrefix = orgBranding
- ? `${orgBranding.slug}.${subdomainSuffix()}`
- : process.env.NEXT_PUBLIC_WEBSITE_URL;
+ const urlPrefix = orgBranding?.fullDomain ?? process.env.NEXT_PUBLIC_WEBSITE_URL;
return (
{!small && (
-
+
{user.name || "Nameless User"}
@@ -778,13 +778,11 @@ type SideBarProps = {
function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
const { status, data } = useSession();
- const router = useRouter();
// Make sure that Sidebar is rendered optimistically so that a refresh of pages when logged in have SideBar from the beginning.
// This improves the experience of refresh on app store pages(when logged in) which are SSG.
// Though when logged out, app store pages would temporarily show SideBar until session status is confirmed.
if (status !== "loading" && status !== "authenticated") return null;
- if (router.route.startsWith("/v2/settings/")) return null;
return ;
}
@@ -819,9 +817,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
},
{
name: "settings",
- href: user?.organizationId
- ? `/settings/teams/${user.organizationId}/profile`
- : "/settings/my-account/profile",
+ href: user?.organizationId ? `/settings/organizations/profile` : "/settings/my-account/profile",
icon: Settings,
},
];
@@ -834,20 +830,16 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
{orgBranding ? (
- {orgBranding ? (
-
-
-
- {orgBranding.name}
-
-
- ) : (
-
- )}
+
+
+
+ {orgBranding.name}
+
+
) : (
diff --git a/packages/features/test/orgDomains.test.ts b/packages/features/test/orgDomains.test.ts
index 5e459f42b6..2fece9b37d 100644
--- a/packages/features/test/orgDomains.test.ts
+++ b/packages/features/test/orgDomains.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
-import { orgDomainConfig, getOrgDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
+import { orgDomainConfig, getOrgSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
import * as constants from "@calcom/lib/constants";
function setupEnvs({ WEBAPP_URL = "https://app.cal.com" } = {}) {
@@ -32,25 +32,25 @@ describe("Org Domains Utils", () => {
});
});
- describe("getOrgDomain", () => {
+ describe("getOrgSlug", () => {
it("should handle a prod web app url with a prod subdomain hostname", () => {
setupEnvs();
- expect(getOrgDomain("acme.cal.com")).toEqual("acme");
+ expect(getOrgSlug("acme.cal.com")).toEqual("acme");
});
it("should handle a prod web app url with a staging subdomain hostname", () => {
setupEnvs();
- expect(getOrgDomain("acme.cal.dev")).toEqual(null);
+ expect(getOrgSlug("acme.cal.dev")).toEqual(null);
});
it("should handle a local web app with port url with a local subdomain hostname", () => {
setupEnvs({ WEBAPP_URL: "http://app.cal.local:3000" });
- expect(getOrgDomain("acme.cal.local:3000")).toEqual("acme");
+ expect(getOrgSlug("acme.cal.local:3000")).toEqual("acme");
});
it("should handle a local web app with port url with a non-local subdomain hostname", () => {
setupEnvs({ WEBAPP_URL: "http://app.cal.local:3000" });
- expect(getOrgDomain("acme.cal.com:3000")).toEqual(null);
+ expect(getOrgSlug("acme.cal.com:3000")).toEqual(null);
});
});
});
diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts
index e63de91bb4..d004180925 100644
--- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts
@@ -25,7 +25,7 @@ const vercelCreateDomain = async (domain: string) => {
const response = await fetch(
`https://api.vercel.com/v8/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`,
{
- body: `{\n "name": "${domain}.${subdomainSuffix()}"\n}`,
+ body: JSON.stringify({ name: `${domain}.${subdomainSuffix()}` }),
headers: {
Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,
"Content-Type": "application/json",
diff --git a/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts b/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
index ebc9fba7ee..2cb920857a 100644
--- a/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/getBrand.handler.ts
@@ -1,3 +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 type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@@ -26,11 +27,15 @@ export const getBrandHandler = async ({ ctx }: VerifyCodeOptions) => {
});
const metadata = teamMetadataSchema.parse(team?.metadata);
- const slug = team?.slug || metadata?.requestedSlug;
+ const slug = (team?.slug || metadata?.requestedSlug) as string;
+ const fullDomain = getOrgFullDomain(slug);
+ const domainSuffix = subdomainSuffix();
return {
...team,
metadata,
slug,
+ fullDomain,
+ domainSuffix,
};
};