{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-change-password",
  "title": "Auth Change Password",
  "description": "Change password with current and new password fields.",
  "registryDependencies": [
    "button",
    "card",
    "field",
    "input-group"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-change-password.tsx",
      "content": "\"use client\";\n\nimport { CheckCircle2, Eye, EyeOff, Loader2, Lock, Shield } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\n\nexport interface AuthChangePasswordProps {\n  onSubmit?: (data: { currentPassword: string; newPassword: string }) => void;\n  className?: string;\n  isLoading?: boolean;\n  isSuccess?: boolean;\n  errors?: {\n    currentPassword?: string;\n    newPassword?: string;\n    confirmPassword?: string;\n    general?: string;\n  };\n  successMessage?: string;\n}\n\ninterface PasswordFieldProps {\n  id: string;\n  label: string;\n  value: string;\n  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n  error?: string;\n  showPassword: boolean;\n  onTogglePassword: () => void;\n  autoComplete: string;\n  placeholder: string;\n  icon: React.ReactNode;\n  description?: string;\n}\n\nfunction PasswordField({\n  id,\n  label,\n  value,\n  onChange,\n  error,\n  showPassword,\n  onTogglePassword,\n  autoComplete,\n  placeholder,\n  icon,\n  description,\n}: PasswordFieldProps) {\n  const errorId = error ? `${id}-error` : undefined;\n\n  return (\n    <Field data-invalid={!!error}>\n      <FieldLabel htmlFor={id}>\n        {label}\n        <span aria-label=\"required\" className=\"text-destructive\">\n          *\n        </span>\n      </FieldLabel>\n      <FieldContent>\n        <InputGroup aria-invalid={!!error}>\n          <InputGroupAddon>{icon}</InputGroupAddon>\n          <InputGroupInput\n            aria-describedby={errorId}\n            aria-invalid={!!error}\n            autoComplete={autoComplete}\n            id={id}\n            name={id}\n            onChange={onChange}\n            placeholder={placeholder}\n            required\n            type={showPassword ? \"text\" : \"password\"}\n            value={value}\n          />\n          <InputGroupButton\n            aria-label={\n              showPassword\n                ? `Hide ${label.toLowerCase()}`\n                : `Show ${label.toLowerCase()}`\n            }\n            className=\"h-full min-w-[32px] touch-manipulation\"\n            onClick={(e) => {\n              e.preventDefault();\n              onTogglePassword();\n            }}\n            type=\"button\"\n          >\n            {showPassword ? (\n              <EyeOff aria-hidden=\"true\" className=\"size-4\" />\n            ) : (\n              <Eye aria-hidden=\"true\" className=\"size-4\" />\n            )}\n          </InputGroupButton>\n        </InputGroup>\n        {error && (\n          <FieldError aria-live=\"polite\" id={errorId}>\n            {error}\n          </FieldError>\n        )}\n        {description && <FieldDescription>{description}</FieldDescription>}\n      </FieldContent>\n    </Field>\n  );\n}\n\ninterface SuccessStateProps {\n  message: string;\n  className?: string;\n}\n\nfunction SuccessState({ message, className }: SuccessStateProps) {\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Password changed</CardTitle>\n        <CardDescription>Your password has been updated</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div\n          aria-live=\"polite\"\n          className=\"flex flex-col items-center gap-4 rounded-lg border border-primary/20 bg-primary/5 p-6 text-center\"\n          role=\"status\"\n        >\n          <div className=\"flex size-12 items-center justify-center rounded-full bg-primary/10\">\n            <CheckCircle2 aria-hidden=\"true\" className=\"size-6 text-primary\" />\n          </div>\n          <p className=\"font-medium text-sm\">{message}</p>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\ninterface ValidationErrors {\n  currentPassword?: string;\n  newPassword?: string;\n  confirmPassword?: string;\n}\n\nfunction validateCurrentPassword(value: string): string | undefined {\n  if (!value.trim()) {\n    return \"Current password is required\";\n  }\n  return;\n}\n\nfunction validateNewPassword(\n  value: string,\n  currentPassword: string\n): string | undefined {\n  if (!value.trim()) {\n    return \"New password is required\";\n  }\n  if (value.length < 8) {\n    return \"Password must be at least 8 characters\";\n  }\n  if (!/(?=.*[a-z])/.test(value)) {\n    return \"Password must contain at least one lowercase letter\";\n  }\n  if (!/(?=.*[A-Z])/.test(value)) {\n    return \"Password must contain at least one uppercase letter\";\n  }\n  if (!/(?=.*\\d)/.test(value)) {\n    return \"Password must contain at least one number\";\n  }\n  if (value === currentPassword) {\n    return \"New password must be different from current password\";\n  }\n  return;\n}\n\nfunction validateConfirmPassword(\n  value: string,\n  passwordValue: string\n): string | undefined {\n  if (!value.trim()) {\n    return \"Please confirm your password\";\n  }\n  if (value !== passwordValue) {\n    return \"Passwords do not match\";\n  }\n  return;\n}\n\nexport default function AuthChangePassword({\n  onSubmit,\n  className,\n  isLoading = false,\n  isSuccess = false,\n  errors,\n  successMessage = \"Your password has been changed successfully.\",\n}: AuthChangePasswordProps) {\n  const [currentPassword, setCurrentPassword] = useState(\"\");\n  const [newPassword, setNewPassword] = useState(\"\");\n  const [confirmPassword, setConfirmPassword] = useState(\"\");\n  const [showCurrentPassword, setShowCurrentPassword] = useState(false);\n  const [showNewPassword, setShowNewPassword] = useState(false);\n  const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n  const [localErrors, setLocalErrors] = useState<ValidationErrors>({});\n\n  const handleCurrentPasswordChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setCurrentPassword(value);\n      if (localErrors.currentPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          currentPassword: validateCurrentPassword(value),\n        }));\n      }\n    },\n    [localErrors.currentPassword]\n  );\n\n  const handleNewPasswordChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setNewPassword(value);\n      if (localErrors.newPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          newPassword: validateNewPassword(value, currentPassword),\n        }));\n      }\n      if (localErrors.confirmPassword && confirmPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          confirmPassword: validateConfirmPassword(confirmPassword, value),\n        }));\n      }\n    },\n    [\n      currentPassword,\n      confirmPassword,\n      localErrors.newPassword,\n      localErrors.confirmPassword,\n    ]\n  );\n\n  const handleConfirmPasswordChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setConfirmPassword(value);\n      if (localErrors.confirmPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          confirmPassword: validateConfirmPassword(value, newPassword),\n        }));\n      }\n    },\n    [newPassword, localErrors.confirmPassword]\n  );\n\n  const handleSubmit = useCallback(\n    (e: React.FormEvent) => {\n      e.preventDefault();\n\n      const currentPasswordError = validateCurrentPassword(currentPassword);\n      const newPasswordError = validateNewPassword(\n        newPassword,\n        currentPassword\n      );\n      const confirmPasswordError = validateConfirmPassword(\n        confirmPassword,\n        newPassword\n      );\n\n      if (currentPasswordError || newPasswordError || confirmPasswordError) {\n        setLocalErrors({\n          currentPassword: currentPasswordError,\n          newPassword: newPasswordError,\n          confirmPassword: confirmPasswordError,\n        });\n        return;\n      }\n\n      setLocalErrors({});\n      onSubmit?.({\n        currentPassword: currentPassword.trim(),\n        newPassword: newPassword.trim(),\n      });\n    },\n    [currentPassword, newPassword, confirmPassword, onSubmit]\n  );\n\n  if (isSuccess) {\n    return <SuccessState className={className} message={successMessage} />;\n  }\n\n  const currentPasswordError =\n    errors?.currentPassword || localErrors.currentPassword;\n  const newPasswordError = errors?.newPassword || localErrors.newPassword;\n  const confirmPasswordError =\n    errors?.confirmPassword || localErrors.confirmPassword;\n  const generalError = errors?.general;\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Change password</CardTitle>\n        <CardDescription>\n          Update your password to keep your account secure\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form className=\"flex flex-col gap-6\" onSubmit={handleSubmit}>\n          {generalError && (\n            <div\n              aria-live=\"polite\"\n              className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm\"\n              role=\"alert\"\n            >\n              {generalError}\n            </div>\n          )}\n\n          <div className=\"flex flex-col gap-4\">\n            <PasswordField\n              autoComplete=\"current-password\"\n              error={currentPasswordError}\n              icon={<Lock aria-hidden=\"true\" className=\"size-4\" />}\n              id=\"change-current-password\"\n              label=\"Current password\"\n              onChange={handleCurrentPasswordChange}\n              onTogglePassword={() => setShowCurrentPassword((prev) => !prev)}\n              placeholder=\"Enter current password…\"\n              showPassword={showCurrentPassword}\n              value={currentPassword}\n            />\n\n            <PasswordField\n              autoComplete=\"new-password\"\n              description=\"Must be at least 8 characters with uppercase, lowercase, and number\"\n              error={newPasswordError}\n              icon={<Shield aria-hidden=\"true\" className=\"size-4\" />}\n              id=\"change-new-password\"\n              label=\"New password\"\n              onChange={handleNewPasswordChange}\n              onTogglePassword={() => setShowNewPassword((prev) => !prev)}\n              placeholder=\"Enter new password…\"\n              showPassword={showNewPassword}\n              value={newPassword}\n            />\n\n            <PasswordField\n              autoComplete=\"new-password\"\n              error={confirmPasswordError}\n              icon={<Lock aria-hidden=\"true\" className=\"size-4\" />}\n              id=\"change-confirm-password\"\n              label=\"Confirm new password\"\n              onChange={handleConfirmPasswordChange}\n              onTogglePassword={() => setShowConfirmPassword((prev) => !prev)}\n              placeholder=\"Confirm new password…\"\n              showPassword={showConfirmPassword}\n              value={confirmPassword}\n            />\n          </div>\n\n          <Button\n            aria-busy={isLoading}\n            className=\"min-h-[44px] w-full touch-manipulation\"\n            data-loading={isLoading}\n            disabled={isLoading}\n            type=\"submit\"\n          >\n            {isLoading ? (\n              <>\n                <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n                Updating password…\n              </>\n            ) : (\n              <>\n                <Shield aria-hidden=\"true\" className=\"size-4\" />\n                Update password\n              </>\n            )}\n          </Button>\n        </form>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}