* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main: - Rebrand Cal.com to Cal.diy across the entire codebase - Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions - Switch license from AGPL-3.0 to MIT - Remove docs/ directory (migrated to Nextra at cal.diy) - Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc. - Clean up .env.example for self-hosted Cal.diy - Update Docker image references to calcom/cal.diy - Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork - Add PR welcome bot for Cal.diy contributors - Fix API v2 breaking changes oasdiff ignore entries - Replace Blacksmith CI runners with default GitHub Actions 3893 files changed, 20789 insertions(+), 411020 deletions(-) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * rip out org related comments in api v2 --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
216 lines
6.8 KiB
TypeScript
216 lines
6.8 KiB
TypeScript
import { timeZoneSchema } from "@calcom/lib/dayjs/timeZone.schema";
|
|
import { emailSchema } from "@calcom/lib/emailSchema";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { MembershipRole } from "@calcom/prisma/enums";
|
|
import { trpc } from "@calcom/trpc/react";
|
|
import { Avatar } from "@calcom/ui/components/avatar";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { Divider } from "@calcom/ui/components/divider";
|
|
import {
|
|
Form,
|
|
Label,
|
|
SelectField,
|
|
TextAreaField,
|
|
TextField,
|
|
ToggleGroup,
|
|
} from "@calcom/ui/components/form";
|
|
import { ImageUploader } from "@calcom/ui/components/image-uploader";
|
|
import { SheetBody, SheetFooter, SheetHeader, SheetTitle } from "@calcom/ui/components/sheet";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
import { TimezoneSelect } from "@calcom/web/modules/timezone/components/TimezoneSelect";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useSession } from "next-auth/react";
|
|
import type { Dispatch } from "react";
|
|
import { useMemo, useState } from "react";
|
|
import { Controller, useForm } from "react-hook-form";
|
|
import { z } from "zod";
|
|
import type { UserTableAction } from "../types";
|
|
import { useEditMode } from "./store";
|
|
|
|
interface MembershipOption {
|
|
value: MembershipRole | string;
|
|
label: string;
|
|
isCustomRole?: boolean;
|
|
}
|
|
|
|
const editSchema = z.object({
|
|
name: z.string(),
|
|
username: z.string(),
|
|
email: emailSchema,
|
|
avatar: z.string(),
|
|
bio: z.string(),
|
|
role: z.union([z.nativeEnum(MembershipRole), z.string()]),
|
|
timeZone: timeZoneSchema,
|
|
});
|
|
|
|
type EditSchema = z.infer<typeof editSchema>;
|
|
|
|
export function EditForm({
|
|
selectedUser,
|
|
avatarUrl,
|
|
domainUrl,
|
|
dispatch,
|
|
}: {
|
|
selectedUser: Record<string, unknown>;
|
|
avatarUrl: string;
|
|
domainUrl: string;
|
|
dispatch: Dispatch<UserTableAction>;
|
|
}) {
|
|
const setEditMode = useEditMode((state) => state.setEditMode);
|
|
const [mutationLoading, setMutationLoading] = useState(false);
|
|
const { t } = useLocale();
|
|
const session = useSession();
|
|
const org = session?.data?.user?.org;
|
|
const utils = trpc.useUtils();
|
|
const form = useForm<EditSchema>({
|
|
resolver: zodResolver(editSchema),
|
|
defaultValues: {
|
|
name: (selectedUser?.name as string) ?? "",
|
|
username: (selectedUser?.username as string) ?? "",
|
|
email: (selectedUser?.email as string) ?? "",
|
|
avatar: avatarUrl,
|
|
bio: (selectedUser?.bio as string) ?? "",
|
|
role: (selectedUser?.role as string) ?? "",
|
|
timeZone: (selectedUser?.timeZone as string) ?? "",
|
|
},
|
|
});
|
|
|
|
const isOwner = org?.role === MembershipRole.OWNER;
|
|
const teamRoles: { id: string; name: string }[] = [];
|
|
|
|
const membershipOptions = useMemo<MembershipOption[]>(() => {
|
|
// Add custom roles if they exist
|
|
if (teamRoles?.length > 0) {
|
|
const roles: MembershipOption[] = [];
|
|
// Add custom roles
|
|
teamRoles.forEach((role) => {
|
|
roles.push({
|
|
value: role.id,
|
|
label: role.name,
|
|
isCustomRole: true,
|
|
});
|
|
});
|
|
|
|
return roles;
|
|
}
|
|
|
|
const options: MembershipOption[] = [
|
|
{
|
|
value: MembershipRole.MEMBER,
|
|
label: t("member"),
|
|
},
|
|
{
|
|
value: MembershipRole.ADMIN,
|
|
label: t("admin"),
|
|
},
|
|
];
|
|
|
|
if (isOwner) {
|
|
options.push({
|
|
value: MembershipRole.OWNER,
|
|
label: t("owner"),
|
|
});
|
|
}
|
|
|
|
return options;
|
|
}, [t, isOwner, teamRoles]);
|
|
|
|
const mutation = { mutate: (..._args: unknown[]) => {}, mutateAsync: async () => ({}), isPending: false };
|
|
|
|
const watchTimezone = form.watch("timeZone");
|
|
|
|
return (
|
|
<>
|
|
<Form
|
|
form={form}
|
|
className="flex h-full flex-col"
|
|
handleSubmit={() => {
|
|
setMutationLoading(true);
|
|
setMutationLoading(false);
|
|
setEditMode(false);
|
|
}}>
|
|
<SheetHeader>
|
|
<SheetTitle>{t("update_profile")}</SheetTitle>
|
|
</SheetHeader>
|
|
<SheetBody className="bg-cal-muted border-subtle mt-6 gap-4 rounded-xl border p-4 stack-y-6">
|
|
<div className="">
|
|
<Controller
|
|
control={form.control}
|
|
name="avatar"
|
|
render={({ field: { value } }) => (
|
|
<div className="flex items-center">
|
|
<Avatar alt={`${selectedUser?.name} avatar`} imageSrc={value} size="mdLg" />
|
|
<div className="ml-4">
|
|
<ImageUploader
|
|
target="avatar"
|
|
id="avatar-upload"
|
|
buttonMsg={t("change_avatar")}
|
|
buttonSize="sm"
|
|
handleAvatarChange={(newAvatar) => {
|
|
form.setValue("avatar", newAvatar, { shouldDirty: true });
|
|
}}
|
|
imageSrc={value || undefined}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
/>
|
|
</div>
|
|
<Divider />
|
|
<div className="stack-y-3">
|
|
<TextField label={t("name")} {...form.register("name")} />
|
|
<TextField label={t("username")} {...form.register("username")} />
|
|
<TextAreaField label={t("about")} {...form.register("bio")} />
|
|
<div>
|
|
<Label>{t("role")}</Label>
|
|
{teamRoles?.length > 0 ? (
|
|
<SelectField
|
|
defaultValue={membershipOptions.find(
|
|
(option) => option.value === ((selectedUser?.role as string) ?? "MEMBER")
|
|
)}
|
|
value={membershipOptions.find((option) => option.value === form.watch("role"))}
|
|
options={membershipOptions}
|
|
onChange={(option: MembershipOption | null) => {
|
|
if (option) {
|
|
form.setValue("role", option.value);
|
|
}
|
|
}}
|
|
/>
|
|
) : (
|
|
<ToggleGroup
|
|
isFullWidth
|
|
defaultValue={(selectedUser?.role as string) ?? "MEMBER"}
|
|
value={form.watch("role")}
|
|
options={membershipOptions}
|
|
onValueChange={(value) => {
|
|
form.setValue("role", value);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<Label>{t("timezone")}</Label>
|
|
<TimezoneSelect value={watchTimezone ?? "America/Los_Angeles"} />
|
|
</div>
|
|
</div>
|
|
</SheetBody>
|
|
<SheetFooter>
|
|
<Button
|
|
color="secondary"
|
|
type="button"
|
|
className="justify-center"
|
|
onClick={() => {
|
|
setEditMode(false);
|
|
}}>
|
|
{t("cancel")}
|
|
</Button>
|
|
|
|
<Button type="submit" className="justify-center">
|
|
{t("update")}
|
|
</Button>
|
|
</SheetFooter>
|
|
</Form>
|
|
</>
|
|
);
|
|
}
|