diff --git a/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx b/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx index ff11d0322..3bc50b228 100644 --- a/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx @@ -25,11 +25,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { - InputOTP, - InputOTPGroup, - InputOTPSlot, -} from "@/components/ui/input-otp"; +import { InputOTP } from "@/components/ui/input-otp"; import { Tooltip, TooltipContent, @@ -423,23 +419,14 @@ export const Enable2FA = () => { )} -
+
Verification Code - - - - - - - - - + autoFocus + /> Enter the 6-digit code from your authenticator app diff --git a/apps/dokploy/components/ui/input-otp.tsx b/apps/dokploy/components/ui/input-otp.tsx index 184e289b8..b65d63508 100644 --- a/apps/dokploy/components/ui/input-otp.tsx +++ b/apps/dokploy/components/ui/input-otp.tsx @@ -1,70 +1,87 @@ -import { OTPInput, OTPInputContext } from "input-otp"; -import { Dot } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; const InputOTP = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, containerClassName, ...props }, ref) => ( - -)); -InputOTP.displayName = "InputOTP"; + HTMLInputElement, + Omit, "onChange"> & { + value: string; + onChange: (value: string) => void; + maxLength: number; + } +>(({ className, value, onChange, maxLength, ...props }, ref) => { + const [focusedIndex, setFocusedIndex] = React.useState(null); + const inputRef = React.useRef(null); + const previousValueRef = React.useRef(value); -const InputOTPGroup = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> ->(({ className, ...props }, ref) => ( -
-)); -InputOTPGroup.displayName = "InputOTPGroup"; + React.useImperativeHandle(ref, () => inputRef.current!); -const InputOTPSlot = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> & { index: number } ->(({ index, className, ...props }, ref) => { - const inputOTPContext = React.useContext(OTPInputContext); - // @ts-ignore - const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + React.useEffect(() => { + if (value !== previousValueRef.current) { + const newLength = value.length; + setFocusedIndex(newLength); + previousValueRef.current = value; + } + }, [value]); + + const handleChange = (e: React.ChangeEvent) => { + const newValue = e.target.value.replace(/\D/g, "").slice(0, maxLength); + onChange(newValue); + }; + + const handleBoxClick = (index: number) => { + inputRef.current?.focus(); + setFocusedIndex(index); + }; + + const slots = Array.from({ length: maxLength }, (_, i) => { + const char = value[i] || ""; + const isActive = + focusedIndex === i || (focusedIndex === null && i === value.length); + const isFilled = !!char; + + return ( +
handleBoxClick(i)} + className={cn( + "relative flex h-11 w-11 items-center justify-center rounded-lg border-2 border-input bg-background text-base font-semibold transition-all cursor-text hover:border-ring/50", + isActive && "border-ring ring-2 ring-ring/20 ring-offset-1", + isFilled && "border-primary/50 bg-primary/5", + className, + )} + > + {char} + {isActive && !char && ( +
+
+
+ )} +
+ ); + }); return ( -
- {char} - {hasFakeCaret && ( -
-
-
- )} +
+ setFocusedIndex(value.length)} + onBlur={() => setFocusedIndex(null)} + autoComplete="one-time-code" + inputMode="numeric" + pattern="[0-9]*" + maxLength={maxLength} + className="absolute inset-0 w-full h-full opacity-0 cursor-default" + style={{ caretColor: "transparent" }} + {...props} + /> +
{slots}
); }); -InputOTPSlot.displayName = "InputOTPSlot"; +InputOTP.displayName = "InputOTP"; -const InputOTPSeparator = React.forwardRef< - React.ElementRef<"div">, - React.ComponentPropsWithoutRef<"div"> ->(({ ...props }, ref) => ( -
- -
-)); -InputOTPSeparator.displayName = "InputOTPSeparator"; - -export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; +export { InputOTP }; diff --git a/apps/dokploy/pages/index.tsx b/apps/dokploy/pages/index.tsx index e589da3b6..aab788ad9 100644 --- a/apps/dokploy/pages/index.tsx +++ b/apps/dokploy/pages/index.tsx @@ -33,11 +33,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { - InputOTP, - InputOTPGroup, - InputOTPSlot, -} from "@/components/ui/input-otp"; +import { InputOTP } from "@/components/ui/input-otp"; import { Label } from "@/components/ui/label"; import { authClient } from "@/lib/auth-client"; import { api } from "@/utils/api"; @@ -253,26 +249,20 @@ export default function Home({ IS_CLOUD }: Props) { onSubmit={onTwoFactorSubmit} className="space-y-4" id="two-factor-form" - autoComplete="off" + autoComplete="on" >
- + - - - - - - - - - + /> Enter the 6-digit code from your authenticator app