Files
calendar/apps/web/modules/teams/views/TeamProfileView.tsx
T
Zachariah K. Sharma 43969cd00c
Create PR containing updated CHANGELOG.md and release packages to NPM once PR is merged / Release (push) Has been cancelled
Run i18n AI automation / Run i18n (push) Has been cancelled
Next.js Bundle Analysis / analyze (push) Has been cancelled
Expand standalone team settings
2026-06-08 11:58:15 -06:00

141 lines
4.4 KiB
TypeScript

"use client";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { Form, TextAreaField, TextField } from "@calcom/ui/components/form";
import { SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
import { useSession } from "next-auth/react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
type Team = RouterOutputs["viewer"]["teams"]["get"];
type FormValues = {
name: string;
slug: string;
bio: string;
logoUrl: string;
};
function isManager(team: Team | undefined, userId: number | undefined) {
if (!team || !userId) return false;
const member = team.members.find((m) => m.userId === userId);
return Boolean(member?.accepted && (member.role === "OWNER" || member.role === "ADMIN"));
}
export function TeamProfileView({ teamId }: { teamId: number }) {
const { t } = useLocale();
const session = useSession();
const utils = trpc.useUtils();
const teamQuery = trpc.viewer.teams.get.useQuery({ teamId });
const canManage = isManager(teamQuery.data, session.data?.user?.id);
const form = useForm<FormValues>({
defaultValues: { name: "", slug: "", bio: "", logoUrl: "" },
});
useEffect(() => {
if (!teamQuery.data) return;
form.reset({
name: teamQuery.data.name ?? "",
slug: teamQuery.data.slug ?? "",
bio: teamQuery.data.bio ?? "",
logoUrl: teamQuery.data.logoUrl ?? "",
});
}, [teamQuery.data, form]);
const updateMutation = trpc.viewer.teams.update.useMutation({
onSuccess: async () => {
await utils.viewer.teams.get.invalidate({ teamId });
await utils.viewer.teams.list.invalidate();
showToast(t("team_updated_successfully"), "success");
},
onError: (error) => showToast(error.message, "error"),
});
if (teamQuery.isPending) {
return (
<SkeletonContainer>
<div className="border-subtle stack-y-6 border-x px-4 py-8 sm:px-6">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-24 w-full" />
</div>
</SkeletonContainer>
);
}
if (teamQuery.error) {
return <Alert severity="error" title={teamQuery.error.message} />;
}
return (
<Form
form={form}
handleSubmit={(values) => {
if (!canManage) return;
updateMutation.mutate({
teamId,
name: values.name.trim(),
slug: values.slug.trim(),
bio: values.bio.trim() || null,
logoUrl: values.logoUrl.trim() || null,
});
}}>
{!canManage ? (
<Alert
severity="info"
className="mb-4"
title={t("you_are_a_member")}
message={t("only_admins_can_edit")}
/>
) : null}
<div className="border-subtle border-x px-4 py-6 sm:px-6">
<div className="space-y-4">
<TextField
{...form.register("name", { required: true, maxLength: 100 })}
label={t("team_name")}
disabled={!canManage}
data-testid="team-profile-name"
/>
<TextField
{...form.register("slug", { required: true, pattern: /^[a-z0-9-]+$/ })}
label={t("team_url")}
addOnLeading="/"
disabled={!canManage}
data-testid="team-profile-slug"
/>
<TextField
{...form.register("logoUrl")}
label={t("logo_url")}
placeholder="https://example.com/logo.png"
disabled={!canManage}
/>
<TextAreaField
{...form.register("bio", { maxLength: 500 })}
label={t("about")}
placeholder={t("team_description_placeholder")}
disabled={!canManage}
rows={4}
/>
</div>
</div>
<SectionBottomActions align="end">
<Button
type="submit"
color="primary"
loading={updateMutation.isPending}
disabled={!canManage || !form.formState.isDirty}
data-testid="team-profile-save">
{t("update")}
</Button>
</SectionBottomActions>
</Form>
);
}