"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; import { useEffect, useLayoutEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import LicenseRequired from "~/ee/common/components/LicenseRequired"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import turndown from "@calcom/lib/turndownService"; import type { Prisma } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; import { Avatar } from "@calcom/ui/components/avatar"; import { Button } from "@calcom/ui/components/button"; import { LinkIconButton } from "@calcom/ui/components/button"; import { Editor } from "@calcom/ui/components/editor"; import { Form } from "@calcom/ui/components/form"; import { Label } from "@calcom/ui/components/form"; import { TextField } from "@calcom/ui/components/form"; import { BannerUploader, ImageUploader } from "@calcom/ui/components/image-uploader"; import { CopyIcon, PlusIcon } from "@coss/ui/icons"; // if I include this in the above barrel import, I get a runtime error that the component is not exported. import { OrgBanner } from "@calcom/ui/components/organization-banner"; import { SkeletonButton, SkeletonContainer, SkeletonText, SkeletonAvatar, } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; import { Tooltip } from "@calcom/ui/components/tooltip"; import OrgAppearanceViewWrapper from "./appearance"; const orgProfileFormSchema = z.object({ name: z.string(), logoUrl: z.string().nullable(), banner: z.string().nullable(), calVideoLogo: z.string().nullable(), bio: z.string(), }); type FormValues = { id: number; name: string; logoUrl: string | null; banner: string | null; bio: string; slug: string; calVideoLogo: string | null; }; const SkeletonLoader = () => { return (
); }; const OrgProfileView = ({ permissions, }: { permissions?: { canRead: boolean; canEdit: boolean; canDelete: boolean; }; }) => { const { t } = useLocale(); const router = useRouter(); const orgBranding = useOrgBranding(); useLayoutEffect(() => { document.body.focus(); }, []); const { data: currentOrganisation, isPending, error, } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {}); useEffect( function refactorMeWithoutEffect() { if (error) { router.replace("/enterprise"); } }, [error, router] ); // Scroll to top when page loads useEffect(() => { if (!isPending && orgBranding && currentOrganisation) { window.scrollTo(0, 0); } }, [isPending, orgBranding, currentOrganisation]); if (isPending || !orgBranding || !currentOrganisation) { return ; } const isBioEmpty = !currentOrganisation || !currentOrganisation.bio || !currentOrganisation.bio.replace("


", "").length; const defaultValues: FormValues = { id: currentOrganisation?.id, name: currentOrganisation?.name || "", logoUrl: currentOrganisation?.logoUrl, banner: currentOrganisation?.bannerUrl || "", bio: currentOrganisation?.bio || "", calVideoLogo: currentOrganisation?.calVideoLogo || "", slug: currentOrganisation?.slug || ((currentOrganisation?.metadata as Prisma.JsonObject)?.requestedSlug as string) || "", }; return ( <> {permissions?.canEdit ? ( <> ) : (

{currentOrganisation?.name}

{!isBioEmpty && ( <> {/* biome-ignore lint/security/noDangerouslySetInnerHtml: Content is sanitized via markdownToSafeHTML */}
)}
{ navigator.clipboard.writeText(orgBranding.fullDomain); showToast("Copied to clipboard", "success"); }}> {t("copy_link_org")}
)} {/* LEAVE ORG should go above here ^ */} ); }; const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => { const utils = trpc.useUtils(); const { t } = useLocale(); const [firstRender, setFirstRender] = useState(true); const form = useForm({ defaultValues, resolver: zodResolver(orgProfileFormSchema), }); const mutation = trpc.viewer.organizations.update.useMutation({ onError: (err) => { // Handle JSON parsing errors from body size limit exceeded if (err.message.includes("Unexpected token") && err.message.includes("Body excee")) { showToast(t("converted_image_size_limit_exceed"), "error"); return; } showToast(err.message, "error"); }, onSuccess: async (res) => { reset({ logoUrl: res.data?.logoUrl, name: (res.data?.name || "") as string, bio: (res.data?.bio || "") as string, slug: defaultValues["slug"], banner: (res.data?.bannerUrl || "") as string, calVideoLogo: (res.data?.calVideoLogo || "") as string, }); await utils.viewer.teams.get.invalidate(); await utils.viewer.organizations.listCurrent.invalidate(); showToast(t("your_organization_updated_sucessfully"), "success"); }, }); const { formState: { isSubmitting, isDirty }, reset, } = form; const isDisabled = isSubmitting || !isDirty; const handleCopy = async (value: string) => { try { await navigator.clipboard.writeText(value); showToast(t("organization_id_copied"), "success"); } catch (error) { showToast(t("error_copying_to_clipboard"), "error"); } }; return (
{ const variables = { logoUrl: values.logoUrl, name: values.name, slug: values.slug, bio: values.bio, banner: values.banner, calVideoLogo: values.calVideoLogo, }; mutation.mutate(variables); }}>
{ const showRemoveLogoButton = value !== null; return (
{showRemoveLogoButton && ( )}
); }} /> { const showRemoveLogoButton = !!value; return (
} size="lg" />
{showRemoveLogoButton && ( )}
); }} />
{ const showRemoveBannerButton = !!value; return ( <>

{t("no_target", { target: "banner" })}

) : ( t("no_target", { target: "banner" }) ) } imageSrc={value} /> {value && (
)} ); }} />
(
{ form.setValue("name", e?.target.value, { shouldDirty: true }); }} />
)} /> (
)} /> (
} />
)} />
md.render(form.getValues("bio") || "")} setText={(value: string) => form.setValue("bio", turndown(value), { shouldDirty: true })} excludedToolbarItems={["blockType"]} disableLists firstRender={firstRender} setFirstRender={setFirstRender} height="80px" />

{t("org_description")}

); }; export default OrgProfileView;