diff --git a/app/DTOs/DynamicField.php b/app/DTOs/DynamicField.php index dc0a6c531..0a4bf2fd3 100644 --- a/app/DTOs/DynamicField.php +++ b/app/DTOs/DynamicField.php @@ -36,6 +36,20 @@ public function text(): self return $this; } + public function password(): self + { + $this->type = 'password'; + + return $this; + } + + public function passwordWithToggle(): self + { + $this->type = 'password-with-toggle'; + + return $this; + } + public function textarea(): self { $this->type = 'textarea'; diff --git a/resources/js/components/ui/dynamic-field.tsx b/resources/js/components/ui/dynamic-field.tsx index 6fd62b7b9..5a814ff5a 100644 --- a/resources/js/components/ui/dynamic-field.tsx +++ b/resources/js/components/ui/dynamic-field.tsx @@ -1,6 +1,7 @@ import { InputHTMLAttributes, useEffect, useState } from 'react'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; +import { PasswordInput } from '@/components/ui/password-input'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -121,6 +122,51 @@ export default function DynamicField({ value, onChange, config, error }: Dynamic ); } + // Handle password + if (config?.type === 'password') { + return ( + + + onChange(e.target.value)} + autoComplete="off" + spellCheck={false} + /> + {config.description &&

{config.description}

} + +
+ ); + } + + // Handle password with visibility toggle + if (config?.type === 'password-with-toggle') { + return ( + + + onChange(e.target.value)} + autoComplete="off" + spellCheck={false} + /> + {config.description &&

{config.description}

} + +
+ ); + } + // Handle server provider select if (config?.type === 'component' && config?.name === 'server_provider') { return ( diff --git a/resources/js/components/ui/password-input.tsx b/resources/js/components/ui/password-input.tsx new file mode 100644 index 000000000..745de5de7 --- /dev/null +++ b/resources/js/components/ui/password-input.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { EyeIcon, EyeOffIcon } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { useInputFocus } from '@/stores/useInputFocus'; + +type PasswordInputProps = Omit, 'type'>; + +const PasswordInput = React.forwardRef(({ className, ...props }, ref) => { + const [showPassword, setShowPassword] = React.useState(false); + const setFocused = useInputFocus((state) => state.setFocused); + + return ( +
+ setFocused(true)} + onBlur={() => setFocused(false)} + {...props} + /> + +
+ ); +}); + +PasswordInput.displayName = 'PasswordInput'; + +export { PasswordInput }; diff --git a/resources/js/types/dynamic-field-config.d.ts b/resources/js/types/dynamic-field-config.d.ts index ca5892808..72a1a0197 100644 --- a/resources/js/types/dynamic-field-config.d.ts +++ b/resources/js/types/dynamic-field-config.d.ts @@ -1,5 +1,5 @@ export interface DynamicFieldConfig { - type: 'text' | 'textarea' | 'select' | 'checkbox' | 'component' | 'alert'; + type: 'text' | 'password' | 'password-with-toggle' | 'textarea' | 'select' | 'checkbox' | 'component' | 'alert'; name: string; options?: string[] | { [key: string]: string }; component?: string;