import { zodResolver } from "@hookform/resolvers/zod"; import type { Prisma } from "@prisma/client"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useLayoutEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import { md } from "@calcom/lib/markdownIt"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import objectKeys from "@calcom/lib/objectKeys"; import slugify from "@calcom/lib/slugify"; import turndown from "@calcom/lib/turndownService"; import { MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import { Avatar, Button, ConfirmationDialogContent, Dialog, DialogTrigger, Editor, Form, ImageUploader, Label, LinkIconButton, Meta, showToast, SkeletonContainer, SkeletonText, TextField, } from "@calcom/ui"; import { ExternalLink, Link as LinkIcon, LogOut, Trash2 } from "@calcom/ui/components/icon"; import { getLayout } from "../../../settings/layouts/SettingsLayout"; const regex = new RegExp("^[a-zA-Z0-9-]*$"); const teamProfileFormSchema = z.object({ name: z.string(), slug: z .string() .regex(regex, { message: "Url can only have alphanumeric characters(a-z, 0-9) and hyphen(-) symbol.", }) .min(1, { message: "Url cannot be left empty" }), logo: z.string(), bio: z.string(), }); const ProfileView = () => { const params = useParamsWithFallback(); const teamId = Number(params.id); const { t } = useLocale(); const router = useRouter(); const utils = trpc.useContext(); const session = useSession(); const [firstRender, setFirstRender] = useState(true); const orgBranding = useOrgBranding(); useLayoutEffect(() => { document.body.focus(); }, []); const mutation = trpc.viewer.teams.update.useMutation({ onError: (err) => { showToast(err.message, "error"); }, async onSuccess() { await utils.viewer.teams.get.invalidate(); showToast(t("your_team_updated_successfully"), "success"); }, }); const form = useForm({ resolver: zodResolver(teamProfileFormSchema), }); const { data: team, isLoading } = trpc.viewer.teams.get.useQuery( { teamId, includeTeamLogo: true }, { enabled: !!teamId, onError: () => { router.push("/settings"); }, onSuccess: (team) => { if (team) { form.setValue("name", team.name || ""); form.setValue("slug", team.slug || ""); form.setValue("bio", team.bio || ""); form.setValue("logo", team.logo || ""); if (team.slug === null && (team?.metadata as Prisma.JsonObject)?.requestedSlug) { form.setValue("slug", ((team?.metadata as Prisma.JsonObject)?.requestedSlug as string) || ""); } } }, } ); const isAdmin = team && (team.membership.role === MembershipRole.OWNER || team.membership.role === MembershipRole.ADMIN); const permalink = `${WEBAPP_URL}/team/${team?.slug}`; const isBioEmpty = !team || !team.bio || !team.bio.replace("


", "").length; const deleteTeamMutation = trpc.viewer.teams.delete.useMutation({ async onSuccess() { await utils.viewer.teams.list.invalidate(); showToast(t("your_team_disbanded_successfully"), "success"); router.push(`${WEBAPP_URL}/teams`); }, }); const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({ async onSuccess() { await utils.viewer.teams.get.invalidate(); await utils.viewer.teams.list.invalidate(); await utils.viewer.eventTypes.invalidate(); showToast(t("success"), "success"); }, async onError(err) { showToast(err.message, "error"); }, }); const publishMutation = trpc.viewer.teams.publish.useMutation({ async onSuccess(data: { url?: string }) { if (data.url) { router.push(data.url); } }, async onError(err) { showToast(err.message, "error"); }, }); function deleteTeam() { if (team?.id) deleteTeamMutation.mutate({ teamId: team.id }); } function leaveTeam() { if (team?.id && session.data) removeMemberMutation.mutate({ teamId: team.id, memberId: session.data.user.id, }); } return ( <> {!isLoading ? ( <> {isAdmin ? (
{ if (team) { const variables = { name: values.name, slug: values.slug, bio: values.bio, logo: values.logo, }; objectKeys(variables).forEach((key) => { if (variables[key as keyof typeof variables] === team?.[key]) delete variables[key]; }); mutation.mutate({ id: team.id, ...variables }); } }}> {!team.parent && ( <>
( <>
{ form.setValue("logo", newLogo); }} imageSrc={value} />
)} />

)} (
{ form.setValue("name", e?.target.value); }} />
)} /> (
{ form.clearErrors("slug"); form.setValue("slug", slugify(e?.target.value, true)); }} />
)} />
md.render(form.getValues("bio") || "")} setText={(value: string) => form.setValue("bio", turndown(value))} excludedToolbarItems={["blockType"]} disableLists firstRender={firstRender} setFirstRender={setFirstRender} />

{t("team_description")}

{IS_TEAM_BILLING_ENABLED && team.slug === null && (team.metadata as Prisma.JsonObject)?.requestedSlug && ( )} ) : (

{team?.name}

{team && !isBioEmpty && ( <>
)}
{t("preview")} { navigator.clipboard.writeText(permalink); showToast("Copied to clipboard", "success"); }}> {t("copy_link_team")}
)}
{t("danger_zone")}
{team?.membership.role === "OWNER" ? ( {t("disband_team_confirmation_message")} ) : ( {t("leave_team_confirmation_message")} )} ) : ( <>

)} ); }; ProfileView.getLayout = getLayout; export default ProfileView;