import { InformationCircleIcon } from "@heroicons/react/outline"; import crypto from "crypto"; import { GetServerSidePropsContext } from "next"; import { useRouter } from "next/router"; import { ComponentProps, FormEvent, RefObject, useEffect, useRef, useState, useMemo } from "react"; import Select, { OptionTypeBase } from "react-select"; import TimezoneSelect, { ITimezone } from "react-timezone-select"; import { QueryCell } from "@lib/QueryCell"; import { asStringOrNull, asStringOrUndefined } from "@lib/asStringOrNull"; import { getSession } from "@lib/auth"; import { nameOfDay } from "@lib/core/i18n/weekday"; import { useLocale } from "@lib/hooks/useLocale"; import { isBrandingHidden } from "@lib/isBrandingHidden"; import showToast from "@lib/notification"; import prisma from "@lib/prisma"; import { trpc } from "@lib/trpc"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import { Dialog, DialogClose, DialogContent } from "@components/Dialog"; import ImageUploader from "@components/ImageUploader"; import SettingsShell from "@components/SettingsShell"; import Shell from "@components/Shell"; import { Alert } from "@components/ui/Alert"; import Avatar from "@components/ui/Avatar"; import Badge from "@components/ui/Badge"; import Button from "@components/ui/Button"; import { UsernameInput } from "@components/ui/UsernameInput"; type Props = inferSSRProps; function HideBrandingInput(props: { hideBrandingRef: RefObject; user: Props["user"] }) { const { t } = useLocale(); const [modelOpen, setModalOpen] = useState(false); return ( <> { if (!e.currentTarget.checked || props.user.plan !== "FREE") { return; } // prevent checking the input e.preventDefault(); setModalOpen(true); }} />

{t("remove_cal_branding_description")}

{" "} {t("to_upgrade_go_to")}{" "} cal.com/upgrade .

); } function SettingsView(props: ComponentProps & { localeProp: string }) { const utils = trpc.useContext(); const { t } = useLocale(); const router = useRouter(); const mutation = trpc.useMutation("viewer.updateProfile", { onSuccess: () => { showToast(t("your_user_profile_updated_successfully"), "success"); setHasErrors(false); // dismiss any open errors }, onError: (err) => { setHasErrors(true); setErrorMessage(err.message); document?.getElementsByTagName("main")[0]?.scrollTo({ top: 0, behavior: "smooth" }); }, async onSettled() { await utils.invalidateQueries(["viewer.i18n"]); }, }); const localeOptions = useMemo(() => { return (router.locales || []).map((locale) => ({ value: locale, // FIXME // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore label: new Intl.DisplayNames(props.localeProp, { type: "language" }).of(locale), })); }, [props.localeProp, router.locales]); const themeOptions = [ { value: "light", label: t("light") }, { value: "dark", label: t("dark") }, ]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const usernameRef = useRef(null!); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const nameRef = useRef(null!); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const descriptionRef = useRef(null!); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const avatarRef = useRef(null!); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const brandColorRef = useRef(null!); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const hideBrandingRef = useRef(null!); const [selectedTheme, setSelectedTheme] = useState(); const [selectedTimeZone, setSelectedTimeZone] = useState(props.user.timeZone); const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: props.user.weekStart, label: nameOfDay(props.localeProp, props.user.weekStart === "Sunday" ? 0 : 1), }); const [selectedLanguage, setSelectedLanguage] = useState({ value: props.localeProp, label: localeOptions.find((option) => option.value === props.localeProp)?.label, }); const [imageSrc, setImageSrc] = useState(props.user.avatar || ""); const [hasErrors, setHasErrors] = useState(false); const [errorMessage, setErrorMessage] = useState(""); useEffect(() => { setSelectedTheme( props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : undefined ); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function updateProfileHandler(event: FormEvent) { event.preventDefault(); const enteredUsername = usernameRef.current.value.toLowerCase(); const enteredName = nameRef.current.value; const enteredDescription = descriptionRef.current.value; const enteredAvatar = avatarRef.current.value; const enteredBrandColor = brandColorRef.current.value; const enteredTimeZone = typeof selectedTimeZone === "string" ? selectedTimeZone : selectedTimeZone.value; const enteredWeekStartDay = selectedWeekStartDay.value; const enteredHideBranding = hideBrandingRef.current.checked; const enteredLanguage = selectedLanguage.value; // TODO: Add validation mutation.mutate({ username: enteredUsername, name: enteredName, bio: enteredDescription, avatar: enteredAvatar, timeZone: enteredTimeZone, weekStart: asStringOrUndefined(enteredWeekStartDay), hideBranding: enteredHideBranding, theme: asStringOrNull(selectedTheme?.value), brandColor: enteredBrandColor, locale: enteredLanguage, }); } return (
{hasErrors && }

{t("change_email_contact")}{" "} help@cal.com

{ avatarRef.current.value = newAvatar; const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, "value" )?.set; nativeInputValueSetter?.call(avatarRef.current, newAvatar); const ev2 = new Event("input", { bubbles: true }); avatarRef.current.dispatchEvent(ev2); updateProfileHandler(ev2 as unknown as FormEvent); setImageSrc(newAvatar); }} imageSrc={imageSrc} />

setSelectedTheme(e.target.checked ? undefined : themeOptions[0])} checked={!selectedTheme} className="w-4 h-4 border-gray-300 rounded-sm focus:ring-neutral-500 text-neutral-900" />

{t("disable_cal_branding_description")}


); } export default function Settings(props: Props) { const { t } = useLocale(); const query = trpc.useQuery(["viewer.i18n"]); return ( } /> ); } export const getServerSideProps = async (context: GetServerSidePropsContext) => { const session = await getSession(context); if (!session?.user?.id) { return { redirect: { permanent: false, destination: "/auth/login" } }; } const user = await prisma.user.findUnique({ where: { id: session.user.id, }, select: { id: true, username: true, name: true, email: true, bio: true, avatar: true, timeZone: true, weekStart: true, hideBranding: true, theme: true, plan: true, brandColor: true, }, }); if (!user) { throw new Error("User seems logged in but cannot be found in the db"); } return { props: { user: { ...user, emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), }, }, }; };