import { signOut, useSession } from "next-auth/react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { identityProviderNameMap } from "@calcom/features/auth/lib/identityProviderNameMap"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { IdentityProvider } from "@calcom/prisma/enums"; import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { Alert, Button, Form, Meta, PasswordField, Select, SettingsToggle, showToast, SkeletonButton, SkeletonContainer, SkeletonText, } from "@calcom/ui"; import PageWrapper from "@components/PageWrapper"; type ChangePasswordSessionFormValues = { oldPassword: string; newPassword: string; sessionTimeout?: number; apiError: string; }; interface PasswordViewProps { user: RouterOutputs["viewer"]["me"]; } const SkeletonLoader = ({ title, description }: { title: string; description: string }) => { return (
); }; const PasswordView = ({ user }: PasswordViewProps) => { const { data } = useSession(); const { t } = useLocale(); const utils = trpc.useContext(); const metadata = userMetadataSchema.safeParse(user?.metadata); const initialSessionTimeout = metadata.success ? metadata.data?.sessionTimeout : undefined; const [sessionTimeout, setSessionTimeout] = useState(initialSessionTimeout); const sessionMutation = trpc.viewer.updateProfile.useMutation({ onSuccess: (data) => { showToast(t("session_timeout_changed"), "success"); formMethods.reset(formMethods.getValues()); setSessionTimeout(data.metadata?.sessionTimeout); }, onSettled: () => { utils.viewer.me.invalidate(); }, onMutate: async () => { await utils.viewer.me.cancel(); const previousValue = await utils.viewer.me.getData(); const previousMetadata = userMetadataSchema.safeParse(previousValue?.metadata); if (previousValue && sessionTimeout && previousMetadata.success) { utils.viewer.me.setData(undefined, { ...previousValue, metadata: { ...previousMetadata?.data, sessionTimeout: sessionTimeout }, }); return { previousValue }; } }, onError: (error, _, context) => { if (context?.previousValue) { utils.viewer.me.setData(undefined, context.previousValue); } showToast(`${t("session_timeout_change_error")}, ${error.message}`, "error"); }, }); const passwordMutation = trpc.viewer.auth.changePassword.useMutation({ onSuccess: () => { showToast(t("password_has_been_changed"), "success"); formMethods.resetField("oldPassword"); formMethods.resetField("newPassword"); if (data?.user.role === "INACTIVE_ADMIN") { /* AdminPasswordBanner component relies on the role returned from the session. Next-Auth doesn't provide a way to revalidate the session cookie, so this a workaround to hide the banner after updating the password. discussion: https://github.com/nextauthjs/next-auth/discussions/4229 */ signOut({ callbackUrl: "/auth/login" }); } }, onError: (error) => { showToast(`${t("error_updating_password")}, ${t(error.message)}`, "error"); formMethods.setError("apiError", { message: t(error.message), type: "custom", }); }, }); const formMethods = useForm({ defaultValues: { oldPassword: "", newPassword: "", }, }); const handleSubmit = (values: ChangePasswordSessionFormValues) => { const { oldPassword, newPassword } = values; if (!oldPassword.length) { formMethods.setError( "oldPassword", { type: "required", message: t("error_required_field") }, { shouldFocus: true } ); } if (!newPassword.length) { formMethods.setError( "newPassword", { type: "required", message: t("error_required_field") }, { shouldFocus: true } ); } if (oldPassword && newPassword) { passwordMutation.mutate({ oldPassword, newPassword }); } }; const timeoutOptions = [5, 10, 15].map((mins) => ({ label: t("multiple_duration_mins", { count: mins }), value: mins, })); const isDisabled = formMethods.formState.isSubmitting || !formMethods.formState.isDirty; const passwordMinLength = data?.user.role === "USER" ? 7 : 15; const isUser = data?.user.role === "USER"; return ( <> {user && user.identityProvider !== IdentityProvider.CAL ? (

{t("account_managed_by_identity_provider", { provider: identityProviderNameMap[user.identityProvider], })}

{t("account_managed_by_identity_provider_description", { provider: identityProviderNameMap[user.identityProvider], })}

) : (
{formMethods.formState.errors.apiError && (
)}

{t("invalid_password_hint", { passwordLength: passwordMinLength })}

{ if (!e) { setSessionTimeout(undefined); if (metadata.success) { sessionMutation.mutate({ metadata: { ...metadata.data, sessionTimeout: undefined }, }); } } else { setSessionTimeout(10); } }} childrenClassName="lg:ml-0" switchContainerClassName={classNames( "py-6 px-4 sm:px-6 border-subtle rounded-xl border", !!sessionTimeout && "rounded-b-none" )}> <>

{t("session_timeout_after")}