import classNames from "classnames"; import { debounce, noop } from "lodash"; import { useRouter } from "next/router"; import { RefCallback, useEffect, useMemo, useState } from "react"; import { getPremiumPlanMode, getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils"; import { fetchUsername } from "@calcom/lib/fetchUsername"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { User } from "@calcom/prisma/client"; import { TRPCClientErrorLike } from "@calcom/trpc/client"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; import type { AppRouter } from "@calcom/trpc/server/routers/_app"; import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Icon, Input, Label, StarIconSolid, } from "@calcom/ui"; export enum UsernameChangeStatusEnum { NORMAL = "NORMAL", UPGRADE = "UPGRADE", DOWNGRADE = "DOWNGRADE", } interface ICustomUsernameProps { currentUsername: string | undefined; setCurrentUsername?: (newUsername: string) => void; inputUsernameValue: string | undefined; usernameRef: RefCallback; setInputUsernameValue: (value: string) => void; onSuccessMutation?: () => void; onErrorMutation?: (error: TRPCClientErrorLike) => void; user: Pick< User, | "username" | "name" | "email" | "bio" | "avatar" | "timeZone" | "weekStart" | "hideBranding" | "theme" | "plan" | "brandColor" | "darkBrandColor" | "timeFormat" | "metadata" >; readonly?: boolean; } const obtainNewUsernameChangeCondition = ({ userIsPremium, isNewUsernamePremium, stripeCustomer, }: { userIsPremium: boolean; isNewUsernamePremium: boolean; stripeCustomer: RouterOutputs["viewer"]["stripeCustomer"] | undefined; }) => { if (!userIsPremium && isNewUsernamePremium && !stripeCustomer?.paidForPremium) { return UsernameChangeStatusEnum.UPGRADE; } if (userIsPremium && !isNewUsernamePremium && getPremiumPlanMode() === "subscription") { return UsernameChangeStatusEnum.DOWNGRADE; } return UsernameChangeStatusEnum.NORMAL; }; const PremiumTextfield = (props: ICustomUsernameProps) => { const { t } = useLocale(); const { currentUsername, setCurrentUsername = noop, inputUsernameValue, setInputUsernameValue, usernameRef, onSuccessMutation, onErrorMutation, readonly: disabled, user, } = props; const [usernameIsAvailable, setUsernameIsAvailable] = useState(false); const [markAsError, setMarkAsError] = useState(false); const router = useRouter(); const { paymentStatus: recentAttemptPaymentStatus } = router.query; const [openDialogSaveUsername, setOpenDialogSaveUsername] = useState(false); const { data: stripeCustomer } = trpc.viewer.stripeCustomer.useQuery(); const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; const [isInputUsernamePremium, setIsInputUsernamePremium] = useState(false); const debouncedApiCall = useMemo( () => debounce(async (username: string) => { const { data } = await fetchUsername(username); setMarkAsError(!data.available && !!currentUsername && username !== currentUsername); setIsInputUsernamePremium(data.premium); setUsernameIsAvailable(data.available); }, 150), [] ); useEffect(() => { // Use the current username or if it's not set, use the one available from stripe setInputUsernameValue(currentUsername || stripeCustomer?.username || ""); }, [setInputUsernameValue, currentUsername, stripeCustomer?.username]); useEffect(() => { if (!inputUsernameValue) { debouncedApiCall.cancel(); return; } debouncedApiCall(inputUsernameValue); }, [debouncedApiCall, inputUsernameValue]); const utils = trpc.useContext(); const updateUsername = trpc.viewer.updateProfile.useMutation({ onSuccess: async () => { onSuccessMutation && (await onSuccessMutation()); setOpenDialogSaveUsername(false); }, onError: (error) => { onErrorMutation && onErrorMutation(error); }, async onSettled() { await utils.viewer.public.i18n.invalidate(); }, }); // when current username isn't set - Go to stripe to check what username he wanted to buy and was it a premium and was it paid for const paymentRequired = !currentUsername && stripeCustomer?.isPremium && !stripeCustomer?.paidForPremium; const usernameChangeCondition = obtainNewUsernameChangeCondition({ userIsPremium: isCurrentUsernamePremium, isNewUsernamePremium: isInputUsernamePremium, stripeCustomer, }); const usernameFromStripe = stripeCustomer?.username; const paymentLink = `/api/integrations/stripepayment/subscription?intentUsername=${ inputUsernameValue || usernameFromStripe }&action=${usernameChangeCondition}&callbackUrl=${router.asPath}`; const ActionButtons = () => { if (paymentRequired) { return (
); } if ((usernameIsAvailable || isInputUsernamePremium) && currentUsername !== inputUsernameValue) { return (
); } return <>; }; const saveUsername = () => { if (usernameChangeCondition === UsernameChangeStatusEnum.NORMAL) { updateUsername.mutate({ username: inputUsernameValue, }); setCurrentUsername(inputUsernameValue); } }; let paymentMsg = !currentUsername ? ( You need to reserve your premium username for {getPremiumPlanPriceValue()} ) : null; if (recentAttemptPaymentStatus && recentAttemptPaymentStatus !== "paid") { paymentMsg = ( Your payment could not be completed. Your username is still not reserved ); } return (
{process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")}/
{ event.preventDefault(); // Reset payment status delete router.query.paymentStatus; setInputUsernameValue(event.target.value); }} data-testid="username-input" />
{isInputUsernamePremium ? : <>} {!isInputUsernamePremium && usernameIsAvailable ? : <>}
{(usernameIsAvailable || isInputUsernamePremium) && currentUsername !== inputUsernameValue && (
)}
{paymentMsg} {markAsError &&

Username is already taken

} {usernameIsAvailable && (

{usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE && ( <>{t("premium_to_standard_username_description")} )}

)}
{usernameChangeCondition && usernameChangeCondition !== UsernameChangeStatusEnum.NORMAL && (

{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && t("change_username_standard_to_premium")} {usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE && t("change_username_premium_to_standard")}

)}

{t("current_username")}

{currentUsername}

{t("new_username")}

{inputUsernameValue}

{/* redirect to checkout */} {(usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE || usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE) && ( )} {/* Normal save */} {usernameChangeCondition === UsernameChangeStatusEnum.NORMAL && ( )} setOpenDialogSaveUsername(false)}> {t("cancel")}
); }; export { PremiumTextfield };