import { IdentityProvider } from "@prisma/client"; import crypto from "crypto"; import { signOut } from "next-auth/react"; import { useRef, useState, BaseSyntheticEvent, useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { ErrorCode } from "@calcom/lib/auth"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { TRPCClientErrorLike } from "@calcom/trpc/client"; import { trpc } from "@calcom/trpc/react"; import { AppRouter } from "@calcom/trpc/server/routers/_app"; import { Icon } from "@calcom/ui"; import { Alert } from "@calcom/ui/Alert"; import { Avatar } from "@calcom/ui/components/avatar"; import { Button } from "@calcom/ui/components/button"; import { Form, Label, TextField, PasswordField } from "@calcom/ui/components/form"; import { Dialog, DialogContent, DialogTrigger } from "@calcom/ui/v2/core/Dialog"; import ImageUploader from "@calcom/ui/v2/core/ImageUploader"; import Meta from "@calcom/ui/v2/core/Meta"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; import showToast from "@calcom/ui/v2/core/notifications"; import { SkeletonContainer, SkeletonText, SkeletonButton, SkeletonAvatar } from "@calcom/ui/v2/core/skeleton"; import TwoFactor from "@components/auth/TwoFactor"; import { UsernameAvailability } from "@components/ui/UsernameAvailability"; const SkeletonLoader = () => { return (
); }; interface DeleteAccountValues { totpCode: string; } const ProfileView = () => { const { t } = useLocale(); const utils = trpc.useContext(); const usernameRef = useRef(null); const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); const mutation = trpc.useMutation("viewer.updateProfile", { onSuccess: () => { showToast(t("settings_updated_successfully"), "success"); }, onError: () => { showToast(t("error_updating_settings"), "error"); }, }); const [confirmPasswordOpen, setConfirmPasswordOpen] = useState(false); const [confirmPasswordErrorMessage, setConfirmPasswordDeleteErrorMessage] = useState(""); const [deleteAccountOpen, setDeleteAccountOpen] = useState(false); const [hasDeleteErrors, setHasDeleteErrors] = useState(false); const [deleteErrorMessage, setDeleteErrorMessage] = useState(""); const [currentUsername, setCurrentUsername] = useState(user?.username || undefined); const [inputUsernameValue, setInputUsernameValue] = useState(currentUsername); useEffect(() => { if (user?.username) setCurrentUsername(user?.username); }, [user?.username]); const form = useForm(); const emailMd5 = crypto .createHash("md5") .update(user?.email || "example@example.com") .digest("hex"); const onDeleteMeSuccessMutation = async () => { await utils.invalidateQueries(["viewer.me"]); showToast(t("Your account was deleted"), "success"); setHasDeleteErrors(false); // dismiss any open errors if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") { signOut({ callbackUrl: "/auth/logout?survey=true" }); } else { signOut({ callbackUrl: "/auth/logout" }); } }; const confirmPasswordMutation = trpc.useMutation("viewer.auth.verifyPassword", { onSuccess() { mutation.mutate(formMethods.getValues()); setConfirmPasswordOpen(false); }, onError() { setConfirmPasswordDeleteErrorMessage(t("incorrect_password")); }, }); const onDeleteMeErrorMutation = (error: TRPCClientErrorLike) => { setHasDeleteErrors(true); setDeleteErrorMessage(errorMessages[error.message]); }; const deleteMeMutation = trpc.useMutation("viewer.deleteMe", { onSuccess: onDeleteMeSuccessMutation, onError: onDeleteMeErrorMutation, async onSettled() { await utils.invalidateQueries(["viewer.me"]); }, }); const deleteMeWithoutPasswordMutation = trpc.useMutation("viewer.deleteMeWithoutPassword", { onSuccess: onDeleteMeSuccessMutation, onError: onDeleteMeErrorMutation, async onSettled() { await utils.invalidateQueries(["viewer.me"]); }, }); const isCALIdentityProviver = user?.identityProvider === IdentityProvider.CAL; const onConfirmPassword = (e: Event | React.MouseEvent) => { e.preventDefault(); const password = passwordRef.current.value; confirmPasswordMutation.mutate({ passwordInput: password }); }; const onConfirmButton = (e: Event | React.MouseEvent) => { e.preventDefault(); if (isCALIdentityProviver) { const totpCode = form.getValues("totpCode"); const password = passwordRef.current.value; deleteMeMutation.mutate({ password, totpCode }); } else { deleteMeWithoutPasswordMutation.mutate(); } }; const onConfirm = ({ totpCode }: DeleteAccountValues, e: BaseSyntheticEvent | undefined) => { e?.preventDefault(); if (isCALIdentityProviver) { const password = passwordRef.current.value; deleteMeMutation.mutate({ password, totpCode }); } else { deleteMeWithoutPasswordMutation.mutate(); } }; const formMethods = useForm<{ avatar?: string; username?: string; name?: string; email?: string; bio?: string; }>(); const { reset } = formMethods; const formInitializedRef = useRef(false); useEffect(() => { // The purpose of reset is to set the initial value obtained from tRPC. // `user` would change for many reasons (e.g. when viewer.me automatically fetches on window re-focus(a react query feature)) if (user && !formInitializedRef.current) { formInitializedRef.current = true; reset({ avatar: user?.avatar || "", username: user?.username || "", name: user?.name || "", email: user?.email || "", bio: user?.bio || "", }); } }, [reset, user]); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const passwordRef = useRef(null!); const errorMessages: { [key: string]: string } = { [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"), [ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`, [ErrorCode.UserNotFound]: t("no_account_exists"), [ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`, [ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`, [ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"), }; const onSuccessfulUsernameUpdate = async () => { showToast(t("settings_updated_successfully"), "success"); await utils.invalidateQueries(["viewer.me"]); }; const onErrorInUsernameUpdate = () => { showToast(t("error_updating_settings"), "error"); }; if (isLoading || !user) return ; return ( <>
{ if (values.email !== user?.email && isCALIdentityProviver) { setConfirmPasswordOpen(true); } else { mutation.mutate(values); } }}>
( <>
{ formMethods.setValue("avatar", newAvatar); }} imageSrc={value} />
)} />

{/* Delete account Dialog */} e && onConfirmButton(e)}> <>

{t("delete_account_confirmation_message")}

{isCALIdentityProviver && ( )} {user?.twoFactorEnabled && isCALIdentityProviver && ( )} {hasDeleteErrors && }
{/* If changing email, confirm password */} e && onConfirmPassword(e)}> <> {confirmPasswordErrorMessage && } ); }; ProfileView.getLayout = getLayout; export default ProfileView;