fix: Creating team and preview URL fixes [CAL-1997] [CAL-1998] (#9648)
* Creating team and preview URL fixes * Renamed function in tests too * fix: Signup from org wizard invitation and other tweaks [CAL-1973] [CAL-1993] [CAL-1994] (#9693) * Fixing signup from org invitation and other tweaks * feat: Organizations no middleware rewrite (#9548) Co-authored-by: Leo Giovanetti <hello@leog.me> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> * Revert "fix: redirect to 404 page for invalid email verification token" (#9703) * New Crowdin translations by Github Action * refactor: Skip license checks for development (#9375) * Allow closing the embed on clicking the black transparent area (#9706) * New Crowdin translations by Github Action * perf: [CAL-1928] Deduplicate eventTypes.getByViewer query (#9700) Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> * perf: Remove useless data on EvenTypes getByViewer endpoint (#9704) * feat: [CAL-910] Pre-select country for phone number (CALCOM-5759) (#9526) Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com> * Fix settings layout + org headings (#9712) * Simplifying prop * Removing restriction to choose username * Formatting --------- Co-authored-by: Efraín Rochín <roae.85@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Afzal Sayed <14029371+afzalsayed96@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com> Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Fix conditional before hook * Fix lints --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Efraín Rochín <roae.85@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Afzal Sayed <14029371+afzalsayed96@users.noreply.github.com> Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com> Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
15 files changed
+117
-135
No files matched your search
@@ -2,7 +2,7 @@ import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
@@ -24,9 +24,9 @@ interface UsernameAvailabilityFieldProps {
|
||||
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
|
||||
return organization
|
||||
? organization.slug
|
||||
? `${organization.slug}.${subdomainSuffix()}/`
|
||||
? getOrgFullDomain(organization.slug, { protocol: false })
|
||||
: organization.metadata && organization.metadata.requestedSlug
|
||||
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}/`
|
||||
? getOrgFullDomain(organization.metadata.requestedSlug, { protocol: false })
|
||||
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
|
||||
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useEffect, useState, memo } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
|
||||
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
|
||||
@@ -371,9 +370,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
<ul ref={parent} className="divide-subtle !static w-full divide-y" data-testid="event-types">
|
||||
{types.map((type, index) => {
|
||||
const embedLink = `${group.profile.slug}/${type.slug}`;
|
||||
const calLink = `${
|
||||
orgBranding ? `${new URL(CAL_URL).protocol}//${orgBranding.slug}.${subdomainSuffix()}` : CAL_URL
|
||||
}/${embedLink}`;
|
||||
const calLink = `${orgBranding?.fullDomain ?? CAL_URL}/${embedLink}`;
|
||||
const isManagedEventType = type.schedulingType === SchedulingType.MANAGED;
|
||||
const isChildrenManagedEventType =
|
||||
type.metadata?.managedEventConfig !== undefined && type.schedulingType !== SchedulingType.MANAGED;
|
||||
@@ -698,10 +695,10 @@ const EventTypeListHeading = ({
|
||||
profile,
|
||||
membershipCount,
|
||||
teamId,
|
||||
orgSlug,
|
||||
}: EventTypeListHeadingProps): JSX.Element => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const orgBranding = useOrgBrandingValues();
|
||||
|
||||
const publishTeamMutation = trpc.viewer.teams.publish.useMutation({
|
||||
onSuccess(data) {
|
||||
@@ -739,9 +736,9 @@ const EventTypeListHeading = ({
|
||||
)}
|
||||
{profile?.slug && (
|
||||
<Link href={`${CAL_URL}/${profile.slug}`} className="text-subtle block text-xs">
|
||||
{orgSlug
|
||||
? `${orgSlug}.${subdomainSuffix()}/${profile.slug}`
|
||||
: `${CAL_URL?.replace("https://", "")}/${profile.slug}`}
|
||||
{orgBranding
|
||||
? `${orgBranding.fullDomain.replace("https://", "").replace("http://", "")}${profile.slug}`
|
||||
: `${CAL_URL?.replace("https://", "").replace("http://", "")}/${profile.slug}`}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,14 @@ import { z } from "zod";
|
||||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
|
||||
|
||||
@@ -30,7 +32,9 @@ type FormValues = {
|
||||
token?: string;
|
||||
};
|
||||
|
||||
export default function Signup({ prepopulateFormValues, token }: inferSSRProps<typeof getServerSideProps>) {
|
||||
type SignupProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Signup({ prepopulateFormValues, token, orgSlug }: SignupProps) {
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
const flags = useFlagMap();
|
||||
@@ -116,7 +120,11 @@ export default function Signup({ prepopulateFormValues, token }: inferSSRProps<t
|
||||
{errors.apiError && <Alert severity="error" message={errors.apiError?.message} />}
|
||||
<div className="space-y-4">
|
||||
<TextField
|
||||
addOnLeading={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/`}
|
||||
addOnLeading={
|
||||
orgSlug
|
||||
? getOrgFullDomain(orgSlug, { protocol: false })
|
||||
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
|
||||
}
|
||||
{...register("username")}
|
||||
required
|
||||
/>
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -65,6 +65,7 @@ export const AboutOrganizationForm = () => {
|
||||
fallback={<Plus className="text-subtle h-6 w-6" />}
|
||||
asChild
|
||||
className="items-center"
|
||||
imageSrc={image}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="ms-4">
|
||||
|
||||
@@ -12,7 +12,9 @@ export type OrganizationBranding =
|
||||
| ({
|
||||
logo?: string | null | undefined;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
slug: string;
|
||||
fullDomain: string;
|
||||
domainSuffix: string;
|
||||
} & z.infer<typeof teamMetadataSchema>)
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
@@ -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()}/`;
|
||||
}
|
||||
@@ -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 = () => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<hr className="border-subtle my-8" />
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex-grow text-sm">
|
||||
<label htmlFor="hide-branding" className="text-default font-medium">
|
||||
{t("disable_cal_branding", { appName: APP_NAME })}
|
||||
</label>
|
||||
<p className="text-subtle">
|
||||
{t("team_disable_cal_branding_description", { appName: APP_NAME })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-none">
|
||||
<Controller
|
||||
control={form.control}
|
||||
defaultValue={currentOrg?.hideBranding ?? false}
|
||||
name="hideBranding"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
defaultChecked={field.value}
|
||||
onCheckedChange={(isChecked) => {
|
||||
form.setValue("hideBranding", isChecked);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex-grow text-sm">
|
||||
<label htmlFor="hide-branding" className="text-default font-medium">
|
||||
{t("hide_book_a_team_member")}
|
||||
</label>
|
||||
<p className="text-subtle">{t("hide_book_a_team_member_description")}</p>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Controller
|
||||
control={form.control}
|
||||
defaultValue={currentOrg?.hideBookATeamMember ?? false}
|
||||
name="hideBookATeamMember"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
defaultChecked={field.value}
|
||||
onCheckedChange={(isChecked) => {
|
||||
form.setValue("hideBookATeamMember", isChecked);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" className="mt-8" type="submit" loading={mutation.isLoading}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
|
||||
@@ -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 = () => {
|
||||
<LinkIconButton
|
||||
Icon={LinkIcon}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(permalink);
|
||||
navigator.clipboard.writeText(orgBranding.fullDomain);
|
||||
showToast("Copied to clipboard", "success");
|
||||
}}>
|
||||
{t("copy_link_org")}
|
||||
|
||||
@@ -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<string | null>(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 } }) => (
|
||||
<div className="flex items-center">
|
||||
<Avatar alt="" imageSrc={value || null} gravatarFallbackMd5="newTeam" size="lg" />
|
||||
<div className="ms-4">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("update")}
|
||||
handleAvatarChange={(newAvatar: string) => {
|
||||
newTeamFormMethods.setValue("logo", newAvatar);
|
||||
createTeamMutation.reset();
|
||||
}}
|
||||
<>
|
||||
<Label>{t("team_logo")}</Label>
|
||||
<div className="flex items-center">
|
||||
<Avatar
|
||||
alt=""
|
||||
imageSrc={value}
|
||||
fallback={<Plus className="text-subtle h-6 w-6" />}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="ms-4">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("update")}
|
||||
handleAvatarChange={(newAvatar: string) => {
|
||||
newTeamFormMethods.setValue("logo", newAvatar);
|
||||
createTeamMutation.reset();
|
||||
}}
|
||||
imageSrc={value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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) {
|
||||
<span className="text-muted block text-xs">
|
||||
{team.slug
|
||||
? orgBranding
|
||||
? `${orgBranding.slug}.${subdomainSuffix()}/${team.slug}`
|
||||
? `${orgBranding.fullDomain}${team.slug}`
|
||||
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/${team.slug}`
|
||||
: "Unpublished team"}
|
||||
</span>
|
||||
@@ -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) {
|
||||
<DropdownItem
|
||||
type="button"
|
||||
target="_blank"
|
||||
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/${team.slug}`}
|
||||
href={`${
|
||||
orgBranding
|
||||
? `${orgBranding.fullDomain}`
|
||||
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/`
|
||||
}${team.slug}`}
|
||||
StartIcon={ExternalLink}>
|
||||
{t("preview_team") as string}
|
||||
</DropdownItem>
|
||||
|
||||
@@ -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 (
|
||||
<Dialog
|
||||
|
||||
@@ -362,7 +362,7 @@ function UserDropdown({ small }: UserDropdownProps) {
|
||||
/>
|
||||
</span>
|
||||
{!small && (
|
||||
<span className="flex flex-grow items-center">
|
||||
<span className="flex flex-grow items-center gap-2">
|
||||
<span className="line-clamp-1 flex-grow text-sm leading-none">
|
||||
<span className="text-emphasis block font-medium">{user.name || "Nameless User"}</span>
|
||||
</span>
|
||||
@@ -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 <SideBar bannersHeight={bannersHeight} user={data?.user} />;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
<header className="items-center justify-between md:hidden lg:flex">
|
||||
{orgBranding ? (
|
||||
<Link href="/event-types" className="px-1.5">
|
||||
{orgBranding ? (
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
<Avatar
|
||||
alt={`${orgBranding.name} logo`}
|
||||
imageSrc={getPlaceholderAvatar(orgBranding.logo, orgBranding.name)}
|
||||
size="xsm"
|
||||
/>
|
||||
<p className="text line-clamp-1 text-sm">
|
||||
<span>{orgBranding.name}</span>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Logo small />
|
||||
)}
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
<Avatar
|
||||
alt={`${orgBranding.name} logo`}
|
||||
imageSrc={getPlaceholderAvatar(orgBranding.logo, orgBranding.name)}
|
||||
size="xsm"
|
||||
/>
|
||||
<p className="text line-clamp-1 text-sm">
|
||||
<span>{orgBranding.name}</span>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div data-testid="user-dropdown-trigger">
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user