This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit 40c786656c3217c789939f196ab09d0929f15f52
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Fri Apr 19 10:59:35 2024 -0400

    Simplified Projet and File creation logic
---
 karavan-app/pom.xml                                |   4 -
 .../camel/karavan/api/ProjectFileResource.java     |  17 +-
 .../apache/camel/karavan/api/ProjectResource.java  |  18 +-
 .../karavan/{shared => model}/Configuration.java   |   2 +-
 .../org/apache/camel/karavan/model/Project.java    |  17 +-
 .../camel/karavan/service/ConfigService.java       |   2 +-
 .../camel/karavan/service/ProjectService.java      | 124 +++++-----
 .../org/apache/camel/karavan/shared/Property.java  |  18 --
 .../apache/camel/karavan/shared/error/Error.java   |  78 -------
 .../camel/karavan/shared/error/ErrorResponse.java  |  59 -----
 .../shared/exception/ValidationException.java      |  40 ----
 .../karavan/shared/validation/SimpleValidator.java |  33 ---
 .../karavan/shared/validation/ValidationError.java |  59 -----
 .../shared/validation/ValidationResult.java        |  45 ----
 .../camel/karavan/shared/validation/Validator.java |  23 --
 .../project/ProjectFileCreateValidator.java        |  34 ---
 .../validation/project/ProjectModifyValidator.java |  40 ----
 karavan-app/src/main/webui/package-lock.json       |  58 -----
 karavan-app/src/main/webui/package.json            |   3 -
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  46 +++-
 .../src/main/webui/src/api/ProjectModels.ts        |  22 +-
 .../src/main/webui/src/api/ProjectService.ts       |  15 --
 .../webui/src/designer/route/DslConnections.tsx    |   2 +-
 .../src/main/webui/src/project/ProjectPanel.tsx    |   4 +-
 .../src/main/webui/src/project/ProjectTitle.tsx    |   6 +-
 .../main/webui/src/project/beans/BeanWizard.tsx    |  18 +-
 .../webui/src/project/files/CreateFileModal.tsx    | 258 +++++----------------
 .../src/project/files/CreateIntegrationModal.tsx   | 176 ++++++++++++++
 .../src/main/webui/src/project/files/FilesTab.tsx  |  10 +-
 .../webui/src/project/files/UploadFileModal.tsx    |   6 +-
 .../main/webui/src/projects/CreateProjectModal.tsx | 186 +++++----------
 .../main/webui/src/projects/DeleteProjectModal.tsx |   1 -
 .../main/webui/src/services/CreateServiceModal.tsx | 194 ----------------
 .../main/webui/src/services/DeleteServiceModal.tsx |  70 ------
 .../src/main/webui/src/services/ServicesPage.tsx   |  15 +-
 karavan-app/src/main/webui/src/util/CodeUtils.ts   |  29 ++-
 karavan-app/src/main/webui/src/util/StringUtils.ts |  10 +
 karavan-app/src/main/webui/src/util/form-util.css  |   9 +-
 .../src/main/webui/src/util/useFormUtil.tsx        |  27 ++-
 39 files changed, 550 insertions(+), 1228 deletions(-)

diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 977e4430..bb52d843 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -128,10 +128,6 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-qute</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-hibernate-validator</artifactId>
-        </dependency>
         <dependency>
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-prometheus</artifactId>
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
index 366c9423..9de9c10b 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
@@ -19,11 +19,11 @@ package org.apache.camel.karavan.api;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.*;
 import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
 import org.apache.camel.karavan.code.CodeService;
 import org.apache.camel.karavan.service.KaravanCacheService;
 import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.model.ProjectFile;
-import org.apache.camel.karavan.validation.project.ProjectFileCreateValidator;
 
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -41,9 +41,6 @@ public class ProjectFileResource {
     @Inject
     CodeService codeService;
 
-    @Inject
-    ProjectFileCreateValidator projectFileCreateValidator;
-
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{projectId}")
@@ -65,11 +62,15 @@ public class ProjectFileResource {
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    public ProjectFile create(ProjectFile file) throws Exception {
+    public Response create(ProjectFile file) throws Exception {
         file.setLastUpdate(Instant.now().toEpochMilli());
-        projectFileCreateValidator.validate(file).failOnError();
-        karavanCacheService.saveProjectFile(file);
-        return file;
+        boolean projectFileExists = 
karavanCacheService.getProjectFile(file.getProjectId(), file.getName()) != null;
+        if (projectFileExists) {
+            return Response.serverError().entity("File with given name already 
exists").build();
+        } else {
+            karavanCacheService.saveProjectFile(file);
+            return Response.ok(file).build();
+        }
     }
 
     @PUT
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
index 846a502b..856d4117 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
@@ -81,8 +81,12 @@ public class ProjectResource {
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    public Project save(Project project) {
-        return projectService.save(project);
+    public Response save(Project project) {
+        try {
+            return Response.ok(projectService.save(project)).build();
+        } catch (Exception e) {
+            return 
Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
+        }
     }
 
     @DELETE
@@ -159,7 +163,7 @@ public class ProjectResource {
                     camelStatus.setStatuses(stats);
                     return camelStatus;
                 }).toList();
-        if (statuses != null && !statuses.isEmpty()) {
+        if (!statuses.isEmpty()) {
             return Response.ok(statuses).build();
         } else {
             return Response.noContent().build();
@@ -170,7 +174,11 @@ public class ProjectResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/copy/{sourceProject}")
-    public Project copy(@PathParam("sourceProject") String sourceProject, 
Project project) {
-        return projectService.copy(sourceProject, project);
+    public Response copy(@PathParam("sourceProject") String sourceProject, 
Project project) {
+        try {
+            return Response.ok(projectService.copy(sourceProject, 
project)).build();
+        } catch (Exception e) {
+            return Response.serverError().entity(e.getMessage()).build();
+        }
     }
 }
\ No newline at end of file
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
similarity index 98%
rename from 
karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java
rename to 
karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
index c95affec..301a5a19 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.karavan.shared;
+package org.apache.camel.karavan.model;
 
 import java.util.List;
 
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
index d2d5e38c..8e5222de 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
@@ -17,8 +17,6 @@
 
 package org.apache.camel.karavan.model;
 
-import jakarta.validation.constraints.NotBlank;
-
 import java.io.Serial;
 import java.io.Serializable;
 import java.time.Instant;
@@ -39,11 +37,8 @@ public class Project implements Serializable {
         ephemeral,
     }
 
-    @NotBlank
     String projectId;
-    @NotBlank
     String name;
-    @NotBlank
     String description;
     String lastCommit;
     Long lastCommitTimestamp;
@@ -127,4 +122,16 @@ public class Project implements Serializable {
         this.type = type;
     }
 
+
+    @Override
+    public String toString() {
+        return "Project{" +
+                "projectId='" + projectId + '\'' +
+                ", name='" + name + '\'' +
+                ", description='" + description + '\'' +
+                ", lastCommit='" + lastCommit + '\'' +
+                ", lastCommitTimestamp=" + lastCommitTimestamp +
+                ", type=" + type +
+                '}';
+    }
 }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
index fca69c3b..7a200892 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
@@ -19,7 +19,7 @@ package org.apache.camel.karavan.service;
 import io.quarkus.runtime.StartupEvent;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.enterprise.event.Observes;
-import org.apache.camel.karavan.shared.Configuration;
+import org.apache.camel.karavan.model.Configuration;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
 import java.nio.file.Files;
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index bc966850..8686b506 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -29,8 +29,6 @@ import org.apache.camel.karavan.docker.DockerForKaravan;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.model.*;
 import org.apache.camel.karavan.shared.Constants;
-import org.apache.camel.karavan.shared.Property;
-import org.apache.camel.karavan.validation.project.ProjectModifyValidator;
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -60,10 +58,6 @@ public class ProjectService implements HealthCheck {
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
-
-    @Inject
-    ProjectModifyValidator projectModifyValidator;
-
     @Inject
     KaravanCacheService karavanCacheService;
 
@@ -185,59 +179,66 @@ public class ProjectService implements HealthCheck {
         }
     }
 
-    public Project save(Project project) {
-        projectModifyValidator.validate(project).failOnError();
-
-        karavanCacheService.saveProject(project);
+    public Project save(Project project) throws Exception {
+        boolean projectIdExists = 
karavanCacheService.getProject(project.getProjectId()) != null;
 
-        ProjectFile appProp = codeService.getApplicationProperties(project);
-        karavanCacheService.saveProjectFile(appProp);
-        if (!ConfigService.inKubernetes()) {
-            ProjectFile projectCompose = 
codeService.createInitialProjectCompose(project);
-            karavanCacheService.saveProjectFile(projectCompose);
-        } else if (kubernetesService.isOpenshift()){
-            ProjectFile projectCompose = 
codeService.createInitialDeployment(project);
-            karavanCacheService.saveProjectFile(projectCompose);
+        if (projectIdExists) {
+            throw new Exception("Project with id " + project.getProjectId() + 
" already exists");
+        } else {
+            karavanCacheService.saveProject(project);
+            ProjectFile appProp = 
codeService.getApplicationProperties(project);
+            karavanCacheService.saveProjectFile(appProp);
+            if (!ConfigService.inKubernetes()) {
+                ProjectFile projectCompose = 
codeService.createInitialProjectCompose(project);
+                karavanCacheService.saveProjectFile(projectCompose);
+            } else if (kubernetesService.isOpenshift()) {
+                ProjectFile projectCompose = 
codeService.createInitialDeployment(project);
+                karavanCacheService.saveProjectFile(projectCompose);
+            }
         }
-
         return project;
     }
 
-    public Project copy(String sourceProjectId, Project project) {
-        projectModifyValidator.validate(project).failOnError();
-
-        Project sourceProject = 
karavanCacheService.getProject(sourceProjectId);
-
-        // Save project
-        karavanCacheService.saveProject(project);
-
-        // Copy files from the source and make necessary modifications
-        Map<String, ProjectFile> filesMap = 
karavanCacheService.getProjectFilesMap(sourceProjectId).entrySet().stream()
-                .filter(e -> !Objects.equals(e.getValue().getName(), 
PROJECT_COMPOSE_FILENAME) &&
-                        !Objects.equals(e.getValue().getName(), 
PROJECT_DEPLOYMENT_JKUBE_FILENAME)
-                )
-                .collect(Collectors.toMap(
-                        e -> GroupedKey.create(project.getProjectId(), 
DEFAULT_ENVIRONMENT, e.getValue().getName()),
-                        e -> {
-                            ProjectFile file = e.getValue();
-                            file.setProjectId(project.getProjectId());
-                            if (Objects.equals(file.getName(), 
APPLICATION_PROPERTIES_FILENAME)) {
-                                modifyPropertyFileOnProjectCopy(file, 
sourceProject, project);
-                            }
-                            return file;
-                        })
-                );
-        karavanCacheService.saveProjectFiles(filesMap);
-
-        if (!ConfigService.inKubernetes()) {
-            ProjectFile projectCompose = 
codeService.createInitialProjectCompose(project);
-            karavanCacheService.saveProjectFile(projectCompose);
-        } else if (kubernetesService.isOpenshift()) {
-            ProjectFile projectCompose = 
codeService.createInitialDeployment(project);
-            karavanCacheService.saveProjectFile(projectCompose);
-        }
+    public Project copy (String sourceProjectId, Project project) throws 
Exception {
+        boolean projectIdExists = 
karavanCacheService.getProject(project.getProjectId()) != null;
 
-        return project;
+        if (projectIdExists) {
+            throw new Exception("Project with id " + project.getProjectId() + 
" already exists");
+        } else {
+
+            Project sourceProject = 
karavanCacheService.getProject(sourceProjectId);
+
+            // Save project
+            karavanCacheService.saveProject(project);
+
+            // Copy files from the source and make necessary modifications
+            Map<String, ProjectFile> filesMap = 
karavanCacheService.getProjectFilesMap(sourceProjectId).entrySet().stream()
+                    .filter(e -> !Objects.equals(e.getValue().getName(), 
PROJECT_COMPOSE_FILENAME) &&
+                            !Objects.equals(e.getValue().getName(), 
PROJECT_DEPLOYMENT_JKUBE_FILENAME)
+                    )
+                    .collect(Collectors.toMap(
+                            e -> GroupedKey.create(project.getProjectId(), 
DEFAULT_ENVIRONMENT, e.getValue().getName()),
+                            e -> {
+                                ProjectFile file = e.getValue();
+                                file.setProjectId(project.getProjectId());
+                                if (Objects.equals(file.getName(), 
APPLICATION_PROPERTIES_FILENAME)) {
+                                    modifyPropertyFileOnProjectCopy(file, 
sourceProject, project);
+                                }
+                                return file;
+                            })
+                    );
+            karavanCacheService.saveProjectFiles(filesMap);
+
+            if (!ConfigService.inKubernetes()) {
+                ProjectFile projectCompose = 
codeService.createInitialProjectCompose(project);
+                karavanCacheService.saveProjectFile(projectCompose);
+            } else if (kubernetesService.isOpenshift()) {
+                ProjectFile projectCompose = 
codeService.createInitialDeployment(project);
+                karavanCacheService.saveProjectFile(projectCompose);
+            }
+
+            return project;
+        }
     }
 
     private void modifyPropertyFileOnProjectCopy(ProjectFile propertyFile, 
Project sourceProject, Project project) {
@@ -447,4 +448,21 @@ public class ProjectService implements HealthCheck {
     public DockerComposeService getProjectDockerComposeService(String 
projectId) {
         return codeService.getDockerComposeService(projectId);
     }
+
+    public enum Property {
+        PROJECT_ID("camel.karavan.project-id=%s"),
+        PROJECT_NAME("camel.karavan.project-name=%s"),
+        PROJECT_DESCRIPTION("camel.karavan.project-description=%s"),
+        GAV("camel.jbang.gav=org.camel.karavan.demo:%s:1");
+
+        private final String keyValueFormatter;
+
+        Property(String keyValueFormatter) {
+            this.keyValueFormatter = keyValueFormatter;
+        }
+
+        public String getKeyValueFormatter() {
+            return keyValueFormatter;
+        }
+    }
 }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/Property.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/Property.java
deleted file mode 100644
index 5958f1e9..00000000
--- a/karavan-app/src/main/java/org/apache/camel/karavan/shared/Property.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.apache.camel.karavan.shared;
-
-public enum Property {
-    PROJECT_ID("camel.karavan.project-id=%s"),
-    PROJECT_NAME("camel.karavan.project-name=%s"),
-    PROJECT_DESCRIPTION("camel.karavan.project-description=%s"),
-    GAV("camel.jbang.gav=org.camel.karavan.demo:%s:1");
-
-    private final String keyValueFormatter;
-
-    Property(String keyValueFormatter) {
-        this.keyValueFormatter = keyValueFormatter;
-    }
-
-    public String getKeyValueFormatter() {
-        return keyValueFormatter;
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/Error.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/Error.java
deleted file mode 100644
index dee5372e..00000000
--- a/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/Error.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.apache.camel.karavan.shared.error;
-
-import java.util.Objects;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class Error {
-    private String field;
-    private String message;
-    private String code;
-
-    public Error(String message) {
-        this.message = message;
-    }
-
-    public Error(String field, String message) {
-        this.field = field;
-        this.message = message;
-    }
-
-    public Error(String field, String message, String code) {
-        this.field = field;
-        this.message = message;
-        this.code = code;
-    }
-
-    public String getField() {
-        return field;
-    }
-
-    public void setField(String field) {
-        this.field = field;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-
-    public void setMessage(String message) {
-        this.message = message;
-    }
-
-    public String getCode() {
-        return code;
-    }
-
-    public void setCode(String code) {
-        this.code = code;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        Error error = (Error) o;
-        return Objects.equals(field, error.field) && Objects.equals(message, 
error.message)
-                && Objects.equals(code, error.code);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(field, message, code);
-    }
-
-    @Override
-    public String toString() {
-        return "Error{" +
-                "field='" + field + '\'' +
-                ", message='" + message + '\'' +
-                ", code='" + code + '\'' +
-                '}';
-    }
-}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/ErrorResponse.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/ErrorResponse.java
deleted file mode 100644
index 9749bdf8..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/error/ErrorResponse.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.apache.camel.karavan.shared.error;
-
-import java.util.Collection;
-import java.util.Date;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class ErrorResponse {
-    private final Date timestamp;
-    private final int status;
-    private final String error;
-    private final String message;
-    private Collection<Error> errors;
-
-    public ErrorResponse(
-            int httpStatus,
-            String reasonPhrase,
-            String message
-    ) {
-        this.timestamp = new Date();
-        this.status = httpStatus;
-        this.error = reasonPhrase;
-        this.message = message;
-    }
-
-    public ErrorResponse(
-            int httpStatus,
-            String reasonPhrase,
-            String message,
-            Collection<Error> errors
-    ) {
-        this.timestamp = new Date();
-        this.status = httpStatus;
-        this.error = reasonPhrase;
-        this.message = message;
-        this.errors = errors;
-    }
-
-    public Date getTimestamp() {
-        return timestamp;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-    public String getError() {
-        return error;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-
-    public Collection<Error> getErrors() {
-        return errors;
-    }
-}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ValidationException.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ValidationException.java
deleted file mode 100644
index c56fb075..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ValidationException.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.apache.camel.karavan.shared.exception;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.camel.karavan.shared.validation.ValidationError;
-
-public class ValidationException extends RuntimeException {
-    private final transient Collection<ValidationError> errors;
-
-    public ValidationException(String message, Collection<ValidationError> 
errors) {
-        super(message);
-        this.errors = errors;
-    }
-
-    public ValidationException(String message, ValidationError error) {
-        super(message);
-        this.errors = List.of(error);
-    }
-
-    public ValidationException(String message) {
-        super(message);
-        this.errors = List.of();
-    }
-
-    public ValidationError getFirstErrorOrNull() {
-        return errors.stream().findFirst().orElse(null);
-    }
-
-    public Collection<ValidationError> getErrors() {
-        return errors;
-    }
-
-    @Override
-    public String toString() {
-        return "ValidationException{" +
-                "errors=" + errors +
-                '}';
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/SimpleValidator.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/SimpleValidator.java
deleted file mode 100644
index b9d095cd..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/SimpleValidator.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.apache.camel.karavan.shared.validation;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.validation.ConstraintViolation;
-import jakarta.validation.Validator;
-
-@ApplicationScoped
-public class SimpleValidator {
-    private final Validator validator;
-
-    public SimpleValidator(Validator validator) {
-        this.validator = validator;
-    }
-
-    public <T> void validate(T object, List<ValidationError> errors) {
-        Set<ConstraintViolation<T>> violations = validator.validate(object);
-
-        violations
-                .forEach(violation -> errors.add(new 
ValidationError(violation.getPropertyPath().toString(), 
violation.getMessage())));
-    }
-
-    public <T> ValidationResult validate(T object) {
-        List<ValidationError> errors = new ArrayList<>();
-
-        validate(object, errors);
-
-        return new ValidationResult(errors);
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationError.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationError.java
deleted file mode 100644
index 7c9e3b39..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationError.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.apache.camel.karavan.shared.validation;
-
-import java.io.Serial;
-import java.io.Serializable;
-import java.util.Objects;
-
-public class ValidationError implements Serializable {
-    @Serial
-    private static final long serialVersionUID = 1905122041950251207L;
-
-    private String field;
-    private String message;
-
-    public ValidationError(String field, String message) {
-        this.field = field;
-        this.message = message;
-    }
-
-    public String getField() {
-        return field;
-    }
-
-    public void setField(String field) {
-        this.field = field;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-
-    public void setMessage(String message) {
-        this.message = message;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        ValidationError that = (ValidationError) o;
-        return Objects.equals(field, that.field) && Objects.equals(message, 
that.message);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(field, message);
-    }
-
-    @Override
-    public String toString() {
-        return "ValidationError{" +
-                "field='" + field + '\'' +
-                ", message='" + message + '\'' +
-                '}';
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationResult.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationResult.java
deleted file mode 100644
index e9563634..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/ValidationResult.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.apache.camel.karavan.shared.validation;
-
-import java.util.List;
-
-import org.apache.camel.karavan.shared.exception.ValidationException;
-
-public class ValidationResult {
-    private List<ValidationError> errors;
-
-    ValidationResult(List<ValidationError> errors) {
-        this.errors = errors;
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    public static class Builder {
-        private List<ValidationError> errors;
-
-        Builder() {}
-
-        public Builder errors(List<ValidationError> errors) {
-            this.errors = errors;
-            return this;
-        }
-
-        public ValidationResult build() {
-            return new ValidationResult(errors);
-        }
-
-        @Override
-        public String toString() {
-            return "Builder{" +
-                    "errors=" + errors +
-                    '}';
-        }
-    }
-
-    public void failOnError() {
-        if (!errors.isEmpty()) {
-            throw new ValidationException("Object failed validation", errors);
-        }
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/Validator.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/Validator.java
deleted file mode 100644
index c48fbfc5..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/shared/validation/Validator.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.apache.camel.karavan.shared.validation;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public abstract class Validator<T> {
-    public ValidationResult validate(T object) {
-        List<ValidationError> errors = new ArrayList<>();
-
-        validationRules(object, errors);
-
-        return ValidationResult.builder()
-                .errors(errors)
-                .build();
-    }
-
-    protected abstract void validationRules(T object, List<ValidationError> 
errors);
-
-    public static class ValidationErrors {
-        private ValidationErrors() {
-        }
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java
deleted file mode 100644
index 0cc12533..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.apache.camel.karavan.validation.project;
-
-import java.util.List;
-
-import org.apache.camel.karavan.service.KaravanCacheService;
-import org.apache.camel.karavan.model.ProjectFile;
-import org.apache.camel.karavan.shared.validation.SimpleValidator;
-import org.apache.camel.karavan.shared.validation.ValidationError;
-import org.apache.camel.karavan.shared.validation.Validator;
-
-import jakarta.enterprise.context.ApplicationScoped;
-
-@ApplicationScoped
-public class ProjectFileCreateValidator extends Validator<ProjectFile> {
-
-    private final SimpleValidator simpleValidator;
-    private final KaravanCacheService karavanCacheService;
-
-    public ProjectFileCreateValidator(SimpleValidator simpleValidator, 
KaravanCacheService karavanCacheService) {
-        this.simpleValidator = simpleValidator;
-        this.karavanCacheService = karavanCacheService;
-    }
-
-    @Override
-    protected void validationRules(ProjectFile value, List<ValidationError> 
errors) {
-        simpleValidator.validate(value, errors);
-
-        boolean projectFileExists = 
karavanCacheService.getProjectFile(value.getProjectId(), value.getName()) != 
null;
-
-        if (projectFileExists) {
-            errors.add(new ValidationError("name", "File with given name 
already exists"));
-        }
-    }
-}
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectModifyValidator.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectModifyValidator.java
deleted file mode 100644
index 166f491e..00000000
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectModifyValidator.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.apache.camel.karavan.validation.project;
-
-import java.util.List;
-
-import org.apache.camel.karavan.service.KaravanCacheService;
-import org.apache.camel.karavan.model.Project;
-import org.apache.camel.karavan.shared.validation.SimpleValidator;
-import org.apache.camel.karavan.shared.validation.ValidationError;
-import org.apache.camel.karavan.shared.validation.Validator;
-
-import jakarta.enterprise.context.ApplicationScoped;
-
-@ApplicationScoped
-public class ProjectModifyValidator extends Validator<Project> {
-    private static final List<String> FORBIDDEN_PROJECT_ID_VALUES = 
List.of("templates", "kamelets");
-
-    private final SimpleValidator simpleValidator;
-    private final KaravanCacheService karavanCacheService;
-
-    public ProjectModifyValidator(SimpleValidator simpleValidator, 
KaravanCacheService karavanCacheService) {
-        this.simpleValidator = simpleValidator;
-        this.karavanCacheService = karavanCacheService;
-    }
-
-
-    @Override
-    protected void validationRules(Project value, List<ValidationError> 
errors) {
-        simpleValidator.validate(value, errors);
-
-        boolean projectIdExists = 
karavanCacheService.getProject(value.getProjectId()) != null;
-
-        if(projectIdExists) {
-            errors.add(new ValidationError("projectId", "Project ID already 
exists"));
-        }
-
-        if(FORBIDDEN_PROJECT_ID_VALUES.contains(value.getProjectId())) {
-            errors.add(new ValidationError("projectId", "'templates' or 
'kamelets' can't be used as project ID"));
-        }
-    }
-}
diff --git a/karavan-app/src/main/webui/package-lock.json 
b/karavan-app/src/main/webui/package-lock.json
index 7b3cfe36..63d4a700 100644
--- a/karavan-app/src/main/webui/package-lock.json
+++ b/karavan-app/src/main/webui/package-lock.json
@@ -8,8 +8,6 @@
       "name": "karavan",
       "version": "4.5.1",
       "dependencies": {
-        "@hookform/error-message": "^2.0.1",
-        "@hookform/resolvers": "^3.3.4",
         "@microsoft/fetch-event-source": "^2.0.1",
         "@monaco-editor/react": "4.6.0",
         "@patternfly/patternfly": "^5.2.1",
@@ -34,7 +32,6 @@
         "react-router-dom": "^6.22.3",
         "rxjs": "7.8.1",
         "uuid": "9.0.1",
-        "yup": "^1.4.0",
         "zustand": "^4.5.2"
       },
       "devDependencies": {
@@ -2557,24 +2554,6 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
-    "node_modules/@hookform/error-message": {
-      "version": "2.0.1",
-      "resolved": 
"https://registry.npmjs.org/@hookform/error-message/-/error-message-2.0.1.tgz";,
-      "integrity": 
"sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==",
-      "peerDependencies": {
-        "react": ">=16.8.0",
-        "react-dom": ">=16.8.0",
-        "react-hook-form": "^7.0.0"
-      }
-    },
-    "node_modules/@hookform/resolvers": {
-      "version": "3.3.4",
-      "resolved": 
"https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz";,
-      "integrity": 
"sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==",
-      "peerDependencies": {
-        "react-hook-form": "^7.0.0"
-      }
-    },
     "node_modules/@humanwhocodes/config-array": {
       "version": "0.11.14",
       "resolved": 
"https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz";,
@@ -17685,11 +17664,6 @@
         "react-is": "^16.13.1"
       }
     },
-    "node_modules/property-expr": {
-      "version": "2.0.6",
-      "resolved": 
"https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz";,
-      "integrity": 
"sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
-    },
     "node_modules/property-information": {
       "version": "6.4.0",
       "resolved": 
"https://registry.npmjs.org/property-information/-/property-information-6.4.0.tgz";,
@@ -20310,11 +20284,6 @@
       "integrity": 
"sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
       "dev": true
     },
-    "node_modules/tiny-case": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz";,
-      "integrity": 
"sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
-    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz";,
@@ -20351,11 +20320,6 @@
         "node": ">=0.6"
       }
     },
-    "node_modules/toposort": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz";,
-      "integrity": 
"sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
-    },
     "node_modules/tough-cookie": {
       "version": "4.1.3",
       "resolved": 
"https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz";,
@@ -22359,28 +22323,6 @@
         "url": "https://github.com/sponsors/sindresorhus";
       }
     },
-    "node_modules/yup": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz";,
-      "integrity": 
"sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
-      "dependencies": {
-        "property-expr": "^2.0.5",
-        "tiny-case": "^1.0.3",
-        "toposort": "^2.0.2",
-        "type-fest": "^2.19.0"
-      }
-    },
-    "node_modules/yup/node_modules/type-fest": {
-      "version": "2.19.0",
-      "resolved": 
"https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz";,
-      "integrity": 
"sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
-      "engines": {
-        "node": ">=12.20"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus";
-      }
-    },
     "node_modules/zustand": {
       "version": "4.5.2",
       "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz";,
diff --git a/karavan-app/src/main/webui/package.json 
b/karavan-app/src/main/webui/package.json
index 4f8d52d4..f8fca861 100644
--- a/karavan-app/src/main/webui/package.json
+++ b/karavan-app/src/main/webui/package.json
@@ -30,8 +30,6 @@
     ]
   },
   "dependencies": {
-    "@hookform/error-message": "^2.0.1",
-    "@hookform/resolvers": "^3.3.4",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@monaco-editor/react": "4.6.0",
     "@patternfly/patternfly": "^5.2.1",
@@ -56,7 +54,6 @@
     "react-router-dom": "^6.22.3",
     "rxjs": "7.8.1",
     "uuid": "9.0.1",
-    "yup": "^1.4.0",
     "zustand": "^4.5.2"
   },
   "devDependencies": {
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx 
b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index e8bc7f59..39f53f4a 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -277,12 +277,34 @@ export class KaravanApi {
         });
     }
 
-    static async postProject(project: Project) {
-        return instance.post('/ui/project', project);
+    static async postProject(project: Project, after: (result: boolean, res: 
AxiosResponse<Project> | any) => void) {
+        try {
+            instance.post('/ui/project', project)
+                .then(res => {
+                    if (res.status === 200) {
+                        after(true, res);
+                    }
+                }).catch(err => {
+                after(false, err);
+            });
+        } catch (error: any) {
+            after(false, error);
+        }
     }
 
-    static async copyProject(sourceProject: string, project: Project) {
-        return instance.post('/ui/project/copy/' + sourceProject, project);
+    static copyProject(sourceProject: string, project: Project, after: 
(result: boolean, res: AxiosResponse<Project> | any) => void) {
+        try {
+            instance.post('/ui/project/copy/' + sourceProject, project)
+                .then(res => {
+                    if (res.status === 200) {
+                        after(true, res);
+                    }
+                }).catch(err => {
+                after(false, err);
+            });
+        } catch (error: any) {
+            after(false, error);
+        }
     }
 
     static async deleteProject(project: Project, deleteContainers: boolean, 
after: (res: AxiosResponse<any>) => void) {
@@ -326,8 +348,20 @@ export class KaravanApi {
         });
     }
 
-    static async saveProjectFile(file: ProjectFile) {
-        return instance.post('/ui/file', file);
+    static async saveProjectFile(file: ProjectFile, after: (result: boolean, 
file: ProjectFile | any) => void) {
+        console.log(file)
+        try {
+            instance.post('/ui/file', file)
+                .then(res => {
+                    if (res.status === 200) {
+                        after(true, res.data);
+                    }
+                }).catch(err => {
+                after(false, err);
+            });
+        } catch (error: any) {
+            after(false, error);
+        }
     }
 
     static async putProjectFile(file: ProjectFile, after: (res: 
AxiosResponse<any>) => void) {
diff --git a/karavan-app/src/main/webui/src/api/ProjectModels.ts 
b/karavan-app/src/main/webui/src/api/ProjectModels.ts
index 49beac67..6b701562 100644
--- a/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -160,14 +160,22 @@ export const ProjectFileTypes: ProjectFileType[] = [
     new ProjectFileType("OTHER", "Other", "*"),
 ];
 
-
-export function getProjectFileType (file: ProjectFile) {
-    if (file.name.endsWith(".camel.yaml")) return ProjectFileTypes.filter(p => 
p.name === "INTEGRATION").map(p => p.title)[0];
-    if (file.name.endsWith(".kamelet.yaml")) return ProjectFileTypes.filter(p 
=> p.name === "KAMELET").map(p => p.title)[0];
-    if (file.name.endsWith(".json")) return ProjectFileTypes.filter(p => 
p.name === "JSON").map(p => p.title)[0];
-    if (file.name.endsWith(".yaml")) return ProjectFileTypes.filter(p => 
p.name === "YAML").map(p => p.title)[0];
+function getProjectFileType (file: ProjectFile) {
+    if (file.name.endsWith(".camel.yaml")) return ProjectFileTypes.filter(p => 
p.name === "INTEGRATION")
+    if (file.name.endsWith(".kamelet.yaml")) return ProjectFileTypes.filter(p 
=> p.name === "KAMELET")
+    if (file.name.endsWith(".json")) return ProjectFileTypes.filter(p => 
p.name === "JSON")
+    if (file.name.endsWith(".yaml")) return ProjectFileTypes.filter(p => 
p.name === "YAML")
     const extension = file.name.substring(file.name.lastIndexOf('.') + 1);
-    const types = ProjectFileTypes.filter(p => p.extension === extension);
+    return ProjectFileTypes.filter(p => p.extension === extension);
+}
+
+export function getProjectFileTypeName (file: ProjectFile) {
+    const types = getProjectFileType(file);
+    return types.length >0 ? types.map(p => p.name)[0] : "OTHER";
+}
+
+export function getProjectFileTypeTitle (file: ProjectFile) {
+    const types = getProjectFileType(file);
     return types.length >0 ? types.map(p => p.title)[0] : "Other";
 }
 
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts 
b/karavan-app/src/main/webui/src/api/ProjectService.ts
index 2281075b..952b95fd 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -235,21 +235,6 @@ export class ProjectService {
         });
     }
 
-    public static async createProject(project: Project) {
-        const result = await KaravanApi.postProject(project);
-        return result.data;
-    }
-
-    public static async copyProject(sourceProject: string, project: Project) {
-        const result = await KaravanApi.copyProject(sourceProject, project);
-        return result.data;
-    }
-
-    public static async createFile(file: ProjectFile) {
-        const result = await KaravanApi.saveProjectFile(file);
-        return result.data;
-    }
-
     public static async createOpenApiFile(file: ProjectFile, generateRest: 
boolean, generateRoutes: boolean, integrationName: string) {
         const result = await KaravanApi.postOpenApi(file, generateRest, 
generateRoutes, integrationName);
         return result.data;
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx 
b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index 1c0b284a..a711780b 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -47,7 +47,7 @@ export function DslConnections() {
         setTons(prevState => {
             const data = new Map<string, string[]>();
             TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t => 
{
-                const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters?.name;
+                const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters.name;
                 if (data.has(key)) {
                     const list = data.get(key) || [];
                     list.push(t.routeId);
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index c98e9d02..b19297f6 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -33,10 +33,10 @@ import {ProjectContainerTab} from 
"./container/ProjectContainerTab";
 import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition";
 import {TopologyTab} from "../topology/TopologyTab";
 import {Buffer} from "buffer";
-import {CreateFileModal} from "./files/CreateFileModal";
 import {ProjectType} from "../api/ProjectModels";
 import {ReadmeTab} from "./readme/ReadmeTab";
 import {BeanWizard} from "./beans/BeanWizard";
+import {CreateIntegrationModal} from "./files/CreateIntegrationModal";
 
 export function ProjectPanel() {
 
@@ -89,7 +89,7 @@ export function ProjectPanel() {
                              }}
                              onSetFile={(fileName) => selectFile(fileName)}
                 />
-                <CreateFileModal types={['INTEGRATION']} 
isKameletsProject={false}/>
+                <CreateIntegrationModal type={'INTEGRATION'} 
isKameletsProject={false}/>
                 <BeanWizard/>
             </>
         )
diff --git a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx 
b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
index 1c43c281..180fa59a 100644
--- a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
@@ -23,10 +23,10 @@ import {
     Text,
     TextContent,
     Flex,
-    FlexItem, Button
+    FlexItem,
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {getProjectFileType} from "../api/ProjectModels";
+import {getProjectFileTypeTitle} from "../api/ProjectModels";
 import {useFileStore, useProjectStore} from "../api/ProjectStore";
 import TopologyIcon from "@patternfly/react-icons/dist/js/icons/topology-icon";
 import FilesIcon from "@patternfly/react-icons/dist/js/icons/folder-open-icon";
@@ -87,7 +87,7 @@ export function ProjectTitle() {
                         <FlexItem>
                             <Flex direction={{default: "row"}}>
                                 <FlexItem>
-                                    <Badge>{getProjectFileType(file)}</Badge>
+                                    
<Badge>{getProjectFileTypeTitle(file)}</Badge>
                                 </FlexItem>
                                 <FlexItem>
                                     <TextContent className="description">
diff --git a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx 
b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
index 896dbe12..9c599762 100644
--- a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
+++ b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
@@ -33,8 +33,6 @@ import {useFilesStore, useFileStore, useProjectStore, 
useWizardStore} from "../.
 import {shallow} from "zustand/shallow";
 import ExclamationCircleIcon from 
'@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
 import {useForm} from "react-hook-form";
-import {yupResolver} from "@hookform/resolvers/yup";
-import * as yup from "yup";
 import {ProjectService} from "../../api/ProjectService";
 import {EventBus} from "../../designer/utils/EventBus";
 import {useResponseErrorHandler} from 
"../../shared/error/UseResponseErrorHandler";
@@ -49,13 +47,6 @@ const BEAN_TEMPLATE_SUFFIX_FILENAME = 
"-bean-template.camel.yaml";
 
 export function BeanWizard() {
 
-    const formValidationSchema = yup.object().shape({
-        filename: yup
-            .string()
-            .matches(/^[a-zA-Z0-9_\-.]+$/, 'Incorrect filename')
-            .required("File name is required"),
-    });
-
     const {
         register,
         setError,
@@ -64,7 +55,6 @@ export function BeanWizard() {
         reset,
         setValue
     } = useForm({
-        resolver: yupResolver(formValidationSchema),
         mode: "onChange",
         defaultValues: {filename: ''}
     });
@@ -74,7 +64,7 @@ export function BeanWizard() {
     ]);
 
     const [project] = useProjectStore((s) => [s.project], shallow);
-    const [operation, setFile, designerTab] = useFileStore((s) => 
[s.operation, s.setFile, s.designerTab], shallow);
+    const [setFile, designerTab] = useFileStore((s) => [s.setFile, 
s.designerTab], shallow);
     const [files] = useFilesStore((s) => [s.files], shallow);
     const [showWizard, setShowWizard] = useWizardStore((s) => [s.showWizard, 
s.setShowWizard], shallow)
     const [templateFiles, setTemplateFiles] = useState<ProjectFile[]>([]);
@@ -116,9 +106,9 @@ export function BeanWizard() {
             }
             const fullFileName = filename + CAMEL_YAML_EXT;
             const file = new ProjectFile(fullFileName, project.projectId, 
code, Date.now());
-            return ProjectService.createFile(file)
-                .then(() => handleOnFormSubmitSuccess(file))
-                .catch((error) => registerResponseErrors(error));
+            // return ProjectService.createFile(file)
+            //     .then(() => handleOnFormSubmitSuccess(file))
+            //     .catch((error) => registerResponseErrors(error));
         }
     }
 
diff --git a/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx 
b/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
index 61af5453..20d76296 100644
--- a/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
+++ b/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
@@ -15,250 +15,106 @@
  * limitations under the License.
  */
 
-import React, {useEffect, useState} from 'react';
+import React, {useEffect} from 'react';
 import {
     Button,
     Modal,
-    FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, TextInput, Alert, Divider, Grid, Text,
+    Alert, FormAlert,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {Integration, KameletTypes, MetadataLabels} from 
"karavan-core/lib/model/IntegrationDefinition";
-import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {useFileStore, useProjectStore} from "../../api/ProjectStore";
-import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels";
-import {CamelUi} from "../../designer/utils/CamelUi";
+import {getProjectFileTypeName, ProjectFile} from "../../api/ProjectModels";
 import {ProjectService} from "../../api/ProjectService";
 import {shallow} from "zustand/shallow";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {KameletApi} from "karavan-core/lib/api/KameletApi";
-import {TypeaheadSelect, Value} from "../../designer/ui/TypeaheadSelect";
-import * as yup from "yup";
-import {useForm} from "react-hook-form";
-import {yupResolver} from "@hookform/resolvers/yup";
-import {useResponseErrorHandler} from 
"../../shared/error/UseResponseErrorHandler";
+import {SubmitHandler, useForm} from "react-hook-form";
 import {EventBus} from "../../designer/utils/EventBus";
-import {AxiosError} from "axios";
-import {isEmpty} from "../../util/StringUtils";
+import {isValidFileName} from "../../util/StringUtils";
+import {useFormUtil} from "../../util/useFormUtil";
+import {KaravanApi} from "../../api/KaravanApi";
+import {CodeUtils} from "../../util/CodeUtils";
 
-interface Props {
-    types: string[],
-    isKameletsProject: boolean
-}
-
-export function CreateFileModal(props: Props) {
-
-    const formValidationSchema = yup.object().shape({
-        name: yup
-            .string()
-            .required("File name is required"),
-    });
-
-    const defaultFormValues = {
-        name: ""
-    };
-
-    const responseToFormErrorFields = new Map<string, string>([
-        ["name", "name"]
-    ]);
+export function CreateFileModal() {
 
+    const [project] = useProjectStore((s) => [s.project], shallow);
+    const [operation, setFile] = useFileStore((s) => [s.operation, s.setFile], 
shallow);
+    const [isReset, setReset] = React.useState(false);
+    const [backendError, setBackendError] = React.useState<string>();
+    const formContext = useForm<ProjectFile>({mode: "all"});
+    const {getTextField} = useFormUtil(formContext);
     const {
-        register,
-        setError,
+        formState: {errors},
         handleSubmit,
-        formState: { errors },
         reset,
-        clearErrors
-    } = useForm({
-        resolver: yupResolver(formValidationSchema),
-        mode: "onChange",
-        defaultValues: defaultFormValues
-    });
-
-
-    const [project] = useProjectStore((s) => [s.project], shallow);
-    const [operation, setFile, designerTab] = useFileStore((s) => 
[s.operation, s.setFile, s.designerTab], shallow);
-    const [name, setName] = useState<string>('');
-    const [fileType, setFileType] = useState<string>();
-    const [kameletType, setKameletType] = useState<KameletTypes>('source');
-    const [selectedKamelet, setSelectedKamelet] = useState<string>();
-    const [globalErrors, registerResponseErrors, resetGlobalErrors] = 
useResponseErrorHandler(
-        responseToFormErrorFields,
-        setError
-    );
+        trigger
+    } = formContext;
 
     useEffect(() => {
-        if (props.types.length > 0) {
-            setFileType(props.types[0]);
-        }
-    }, [props]);
+        reset(new ProjectFile('', project.projectId, '', 0));
+        setBackendError(undefined);
+        setReset(true);
+    }, [reset, operation]);
 
-    function resetForm() {
-        resetGlobalErrors();
-        reset(defaultFormValues);
-        setName("")
-        setFileType(props.types.at(0) || 'INTEGRATION');
-    }
+    React.useEffect(() => {
+        isReset && trigger();
+    }, [trigger, isReset]);
 
     function closeModal() {
         setFile("none");
-        resetForm();
-    }
-
-    function handleFormSubmit() {
-        const code = getCode();
-        const fullFileName = getFullFileName(name, fileType);
-        const file = new ProjectFile(fullFileName, project.projectId, code, 
Date.now());
-
-        return ProjectService.createFile(file)
-            .then(() => handleOnFormSubmitSuccess(code, file))
-            .catch((error) => handleOnFormSubmitFailure(error));
     }
 
-    function handleOnFormSubmitSuccess (code: string, file: ProjectFile) {
-        const message = "File successfully created.";
-        EventBus.sendAlert( "Success", message, "success");
-
-        ProjectService.refreshProjectData(file.projectId);
-
-        resetForm();
-        if (code) {
-            setFile('select', file, designerTab);
-        } else {
-            setFile("none");
-        }
+    const onSubmit: SubmitHandler<ProjectFile> = (data) => {
+        data.projectId = project.projectId;
+        data.code = CodeUtils.getCodeForNewFile(data.name, 
getProjectFileTypeName(data));
+        KaravanApi.saveProjectFile(data, (result, file) => {
+            if (result) {
+                onSuccess(file);
+            } else {
+                setBackendError(file?.response?.data);
+            }
+        })
     }
 
-    function handleOnFormSubmitFailure(error: AxiosError) {
-        registerResponseErrors(error);
+    function onSuccess (file: ProjectFile) {
+        EventBus.sendAlert( "Success", "File successfully created", "success");
+        ProjectService.refreshProjectData(project.projectId);
+        setFile('select', file);
     }
 
     function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>): void {
-        if (event.key === 'Enter' && !isEmpty(name)) {
-            handleFormSubmit();
-            event.preventDefault();
-        }
-    }
-
-    function getCode(): string {
-        if (fileType === 'INTEGRATION') {
-            return 
CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain'));
-        } else if (fileType === 'KAMELET') {
-            const kameletName = name + (isKamelet ? '-' + kameletType : '');
-            const integration = Integration.createNew(kameletName, 'kamelet');
-            const meta: MetadataLabels = new 
MetadataLabels({"camel.apache.org/kamelet.type": kameletType});
-            integration.metadata.labels = meta;
-            if (selectedKamelet !== undefined && selectedKamelet !== '') {
-                const kamelet= KameletApi.getKamelets().filter(k => 
k.metadata.name === selectedKamelet).at(0);
-                if (kamelet) {
-                    (integration as any).spec = kamelet.spec;
-                    (integration as any).metadata.labels = 
kamelet.metadata.labels;
-                    (integration as any).metadata.annotations = 
kamelet.metadata.annotations;
-                    const i = CamelUtil.cloneIntegration(integration);
-                    return CamelDefinitionYaml.integrationToYaml(i);
-                }
-            }
-            return CamelDefinitionYaml.integrationToYaml(integration);
-        } else {
-            return '';
+        if (event.key === 'Enter') {
+            handleSubmit(onSubmit)()
         }
     }
 
-    function fileNameCheck(title: string) {
-        return title.replace(/[^0-9a-zA-Z.]+/gi, "-").toLowerCase();
-    }
-
-    const isKamelet = props.isKameletsProject;
-
-    const listOfValues: Value[] = KameletApi.getKamelets()
-        .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === 
kameletType)
-        .map(k => {
-            const v: Value = {value: k.metadata.name, children: 
k.spec.definition.title}
-            return v;
-        })
-
-    function getFullFileName(name: string, type?: string) {
-        let extension = ProjectFileTypes.filter(value => value.name === 
type)[0]?.extension;
-        extension = extension === '*' ? '' : '.' + extension;
-        const filename = (extension !== '.java')
-            ? fileNameCheck(name)
-            : CamelUi.javaNameFromTitle(name);
-        return filename + (isKamelet ? '-' + kameletType : '') + extension;
-    }
-
-    function update(value: string, type?: string) {
-        setName(value);
-        setFileType(type);
-    }
-
     return (
         <Modal
-            title={"Create " + (isKamelet ? "Kamelet" : "")}
+            title="Create file"
             variant={ModalVariant.small}
             isOpen={["create", "copy"].includes(operation)}
             onClose={closeModal}
             onKeyDown={onKeyDown}
             actions={[
-                <Button key="confirm" variant="primary" 
onClick={handleSubmit(handleFormSubmit)}>Save</Button>,
+                <Button key="confirm" variant="primary" 
onClick={handleSubmit(onSubmit)}
+                        isDisabled={Object.getOwnPropertyNames(errors).length 
> 0}
+                >
+                    Save
+                </Button>,
                 <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
             ]}
         >
             <Form autoComplete="off" isHorizontal className="create-file-form">
-                {!isKamelet && <FormGroup label="Type" fieldId="type" 
isRequired>
-                    <ToggleGroup aria-label="Type" isCompact>
-                        {ProjectFileTypes.filter(p => 
props.types.includes(p.name))
-                            .map(p => {
-                                const title = p.title + ' (' + p.extension + 
')';
-                                return <ToggleGroupItem key={title} 
text={title} buttonId={p.name}
-                                                        isSelected={fileType 
=== p.name}
-                                                        onChange={(_, 
selected) => {
-                                                            
resetGlobalErrors();
-                                                            
clearErrors('name');
-                                                            update(name, 
p.name);
-                                                        }}/>
-                            })}
-                    </ToggleGroup>
-                </FormGroup>}
-                {isKamelet && <FormGroup label="Type" fieldId="kameletType" 
isRequired>
-                    <ToggleGroup aria-label="Kamelet Type">
-                        {['source', 'action', 'sink'].map((type) => {
-                            const title = CamelUtil.capitalizeName(type);
-                            return <ToggleGroupItem key={type} text={title} 
buttonId={type}
-                                                    isSelected={kameletType 
=== type}
-                                                    onChange={(_, selected) => 
{
-                                                        setKameletType(type as 
KameletTypes);
-                                                        
setSelectedKamelet(undefined)
-                                                    }}/>
-                        })}
-                    </ToggleGroup>
-                </FormGroup>}
-                <FormGroup label="Name" fieldId="name" isRequired>
-                    <TextInput className="text-field" type="text" id="name"
-                               aria-label="name"
-                               value={name}
-                               validated={!!errors.name ? 'error' : 'default'}
-                               {...register('name')}
-                               onChange={(e, value) => {
-                                   update(value, fileType);
-                                   register('name').onChange(e);
-                               }}
-                    />
-                    {!!errors.name && <Text  style={{ color: 'red', fontStyle: 
'italic'}}>{errors?.name?.message}</Text>}
-                </FormGroup>
-                {isKamelet && <FormGroup label="Copy from" fieldId="kamelet">
-                    <TypeaheadSelect listOfValues={listOfValues} 
onSelect={value => {
-                        setSelectedKamelet(value)
-                    }}/>
-                </FormGroup>}
-                <Grid>
-                    {globalErrors &&
-                        globalErrors.map((error) => (
-                            <Alert title={error} key={error} 
variant="danger"></Alert>
-                        ))}
-                    <Divider role="presentation" />
-                </Grid>
+                {getTextField('name', 'Name', {
+                    regex: v => isValidFileName(v) || 'Not a valid filename',
+                    length: v => v.length > 5 || 'File name should be longer 
that 5 characters',
+                    name: v => !['templates', 'kamelets', 
'karavan'].includes(v) || "'templates', 'kamelets', 'karavan' can't be used as 
filename",
+                })}
+                {backendError &&
+                    <FormAlert>
+                        <Alert variant="danger" title={backendError} 
aria-live="polite" isInline />
+                    </FormAlert>
+                }
             </Form>
         </Modal>
     )
diff --git 
a/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx 
b/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx
new file mode 100644
index 00000000..109b81c4
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/files/CreateIntegrationModal.tsx
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useEffect, useState} from 'react';
+import {
+    Button,
+    Modal,
+    FormGroup,
+    ModalVariant,
+    Form,
+    ToggleGroupItem, ToggleGroup, Alert, FormAlert, capitalize,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {KameletTypes} from "karavan-core/lib/model/IntegrationDefinition";
+import {useFileStore, useProjectStore} from "../../api/ProjectStore";
+import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels";
+import {ProjectService} from "../../api/ProjectService";
+import {shallow} from "zustand/shallow";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {KameletApi} from "karavan-core/lib/api/KameletApi";
+import {TypeaheadSelect, Value} from "../../designer/ui/TypeaheadSelect";
+import {SubmitHandler, useForm} from "react-hook-form";
+import {EventBus} from "../../designer/utils/EventBus";
+import {isValidFileName} from "../../util/StringUtils";
+import {useFormUtil} from "../../util/useFormUtil";
+import {KaravanApi} from "../../api/KaravanApi";
+import {CodeUtils} from "../../util/CodeUtils";
+
+interface Props {
+    type: string,
+    isKameletsProject: boolean
+}
+
+export function CreateIntegrationModal(props: Props) {
+
+    const [project] = useProjectStore((s) => [s.project], shallow);
+    const [operation, setFile, designerTab] = useFileStore((s) => 
[s.operation, s.setFile, s.designerTab], shallow);
+    const [fileType, setFileType] = useState<string>('INTEGRATION');
+    const [kameletType, setKameletType] = useState<KameletTypes>('source');
+    const [selectedKamelet, setSelectedKamelet] = useState<string>();
+    const [isReset, setReset] = React.useState(false);
+    const [backendError, setBackendError] = React.useState<string>();
+    const formContext = useForm<ProjectFile>({mode: "all"});
+    const {getTextFieldSuffix} = useFormUtil(formContext);
+    const {
+        formState: {errors},
+        handleSubmit,
+        reset,
+        trigger
+    } = formContext;
+
+    useEffect(() => {
+        reset(new ProjectFile('', project.projectId, '', 0));
+        setBackendError(undefined);
+        setReset(true);
+    }, [reset, operation]);
+
+    React.useEffect(() => {
+        isReset && trigger();
+    }, [trigger, isReset]);
+
+    function closeModal() {
+        setFile("none");
+    }
+
+    const onSubmit: SubmitHandler<ProjectFile> = (data) => {
+        data.projectId = project.projectId;
+        data.name = getFullFileName(data.name, props.type);
+        data.code = CodeUtils.getCodeForNewFile(data.name, fileType, 
selectedKamelet);
+        KaravanApi.saveProjectFile(data, (result, file) => {
+            if (result) {
+                onSuccess(file);
+            } else {
+                setBackendError(file?.response?.data);
+            }
+        })
+    }
+
+    function onSuccess (file: ProjectFile) {
+        EventBus.sendAlert( "Success", "File successfully created", "success");
+        ProjectService.refreshProjectData(project.projectId);
+        if (file.code) {
+            setFile('select', file, designerTab);
+        } else {
+            setFile("none");
+        }
+    }
+
+    function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>): void {
+        if (event.key === 'Enter') {
+            handleSubmit(onSubmit)()
+        }
+    }
+
+    const isKamelet = props.isKameletsProject;
+
+    const listOfValues: Value[] = KameletApi.getKamelets()
+        .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === 
kameletType)
+        .map(k => {
+            const v: Value = {value: k.metadata.name, children: 
k.spec.definition.title}
+            return v;
+        })
+
+    function getFileExtension(type?: string) {
+        let extension = ProjectFileTypes.filter(value => value.name === 
type)[0]?.extension;
+        extension = extension === '*' ? '' : '.' + extension;
+        return extension;
+    }
+
+    function getFullFileName(name: string, type?: string) {
+        return name + (isKamelet ? '-' + kameletType : '') + 
getFileExtension(type);
+    }
+
+    return (
+        <Modal
+            title={"Create " + (isKamelet ? "Kamelet" : capitalize(designerTab 
|| ' '))}
+            variant={ModalVariant.small}
+            isOpen={["create", "copy"].includes(operation)}
+            onClose={closeModal}
+            onKeyDown={onKeyDown}
+            actions={[
+                <Button key="confirm" variant="primary" 
onClick={handleSubmit(onSubmit)}
+                        isDisabled={Object.getOwnPropertyNames(errors).length 
> 0}
+                >
+                    Save
+                </Button>,
+                <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
+            ]}
+        >
+            <Form autoComplete="off" isHorizontal className="create-file-form">
+                {isKamelet && <FormGroup label="Type" fieldId="kameletType" 
isRequired>
+                    <ToggleGroup aria-label="Kamelet Type">
+                        {['source', 'action', 'sink'].map((type) => {
+                            const title = CamelUtil.capitalizeName(type);
+                            return <ToggleGroupItem key={type} text={title} 
buttonId={type}
+                                                    isSelected={kameletType 
=== type}
+                                                    onChange={(_, selected) => 
{
+                                                        setKameletType(type as 
KameletTypes);
+                                                        
setSelectedKamelet(undefined)
+                                                    }}/>
+                        })}
+                    </ToggleGroup>
+                </FormGroup>}
+                {getTextFieldSuffix('name', 'Name',  
getFileExtension(props.type), true, {
+                    regex: v => isValidFileName(v) || 'Only characters, 
numbers and dashes allowed',
+                    length: v => v.length > 3 || 'File name should be longer 
that 3 characters',
+                    name: v => !['templates', 'kamelets', 
'karavan'].includes(v) || "'templates', 'kamelets', 'karavan' can't be used as 
project",
+                })}
+                {isKamelet && <FormGroup label="Copy from" fieldId="kamelet">
+                    <TypeaheadSelect listOfValues={listOfValues} 
onSelect={value => {
+                        setSelectedKamelet(value)
+                    }}/>
+                </FormGroup>}
+                {backendError &&
+                    <FormAlert>
+                        <Alert variant="danger" title={backendError} 
aria-live="polite" isInline />
+                    </FormAlert>
+                }
+            </Form>
+        </Modal>
+    )
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx 
b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index b9c181ee..a686bab0 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -36,7 +36,11 @@ import {Table} from '@patternfly/react-table/deprecated';
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {useFilesStore, useFileStore, useProjectStore} from 
"../../api/ProjectStore";
-import {getProjectFileType, Project, ProjectFile, ProjectFileTypes} from 
"../../api/ProjectModels";
+import {
+    getProjectFileTypeTitle,
+    ProjectFile,
+    ProjectFileTypes
+} from "../../api/ProjectModels";
 import {FileToolbar} from "./FilesToolbar";
 import DownloadIcon from 
"@patternfly/react-icons/dist/esm/icons/download-icon";
 import FileSaver from "file-saver";
@@ -107,7 +111,7 @@ export function FilesTab () {
                     </Thead>
                     <Tbody>
                         {files.map(file => {
-                            const type = getProjectFileType(file)
+                            const type = getProjectFileTypeTitle(file)
                             return <Tr key={file.name}>
                                 <Td>
                                     <Badge>{type}</Badge>
@@ -159,7 +163,7 @@ export function FilesTab () {
                 </Table>
             </div>
             <UploadFileModal projectId={project.projectId}/>
-            <CreateFileModal types={types} 
isKameletsProject={isKameletsProject()}/>
+            <CreateFileModal/>
             <DeleteFileModal />
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx 
b/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
index 2a69e161..e3c11a5c 100644
--- a/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
+++ b/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
@@ -95,9 +95,9 @@ export function UploadFileModal(props: Props) {
                 .then(() => handleOnFormSubmitSuccess())
                 .catch((error) => handleOnFormSubmitFailure(error));
         } else {
-            return ProjectService.createFile(file)
-                .then(() => handleOnFormSubmitSuccess())
-                .catch((error) => handleOnFormSubmitFailure(error));
+            // return ProjectService.createFile(file)
+            //     .then(() => handleOnFormSubmitSuccess())
+            //     .catch((error) => handleOnFormSubmitFailure(error));
         }
     }
 
diff --git a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx 
b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
index 2e1d670d..be05a643 100644
--- a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
@@ -15,117 +15,80 @@
  * limitations under the License.
  */
 
-import React, {useState} from 'react';
+import React, {useEffect} from 'react';
 import {
     Alert,
     Button,
-    Divider,
-    Form,
-    FormGroup,
-    Grid,
+    Form, FormAlert,
     Modal,
     ModalVariant,
-    Text,
-    TextInput
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {Project} from "../api/ProjectModels";
-import {CamelUi} from "../designer/utils/CamelUi";
-import {isEmpty} from "../util/StringUtils";
+import {isValidProjectId} from "../util/StringUtils";
 import {EventBus} from "../designer/utils/EventBus";
-import {useResponseErrorHandler} from 
"../shared/error/UseResponseErrorHandler";
-import {useForm} from "react-hook-form";
-import * as yup from 'yup';
-import {yupResolver} from '@hookform/resolvers/yup';
-import {AxiosError} from "axios";
+import {SubmitHandler, useForm} from "react-hook-form";
+import {useFormUtil} from "../util/useFormUtil";
+import {KaravanApi} from "../api/KaravanApi";
+import {AxiosResponse} from "axios";
 
-export function CreateProjectModal () {
-
-    const formValidationSchema = yup.object().shape({
-        name: yup
-            .string()
-            .required("Project name is required"),
-        description: yup
-            .string()
-            .required("Project description is required"),
-        projectId: yup
-            .string()
-            .required("Project ID is required")
-            .notOneOf(['templates', 'kamelets'], "'templates' or 'kamelets' 
can't be used as project ID")
-    });
-
-    const defaultFormValues = {
-        name: "",
-        description: "",
-        projectId: ""
-    };
-
-    const responseToFormErrorFields = new Map<string, string>([
-        ["projectId", "projectId"],
-        ["name", "name"],
-        ["description", "description"]
-    ]);
+export function CreateProjectModal() {
 
+    const {project, operation, setOperation} = useProjectStore();
+    const [isReset, setReset] = React.useState(false);
+    const [backendError, setBackendError] = React.useState<string>();
+    const formContext = useForm<Project>({mode: "all"});
+    const {getTextField} = useFormUtil(formContext);
     const {
-        register,
-        setError,
+        formState: {errors},
         handleSubmit,
-        formState: { errors },
-        reset
-    } = useForm({
-        resolver: yupResolver(formValidationSchema),
-        mode: "onChange",
-        defaultValues: defaultFormValues
-    });
+        reset,
+        trigger
+    } = formContext;
 
-    const {project, operation, setOperation} = useProjectStore();
-    const [name, setName] = useState('');
-    const [description, setDescription] = useState('');
-    const [projectId, setProjectId] = useState('');
-    const [globalErrors, registerResponseErrors, resetGlobalErrors] = 
useResponseErrorHandler(
-        responseToFormErrorFields,
-        setError
-    );
+    useEffect(() => {
+        reset(new Project());
+        setBackendError(undefined);
+        setReset(true);
+    }, [reset]);
 
-    function resetForm() {
-        resetGlobalErrors();
-        reset(defaultFormValues);
-    }
+    React.useEffect(() => {
+        isReset && trigger();
+    }, [trigger, isReset]);
 
     function closeModal() {
         setOperation("none");
-        resetForm();
     }
 
-    function handleFormSubmit() {
-        const action = operation !== "copy" ?
-            ProjectService.createProject(new Project({name: name, description: 
description, projectId: projectId})) :
-            ProjectService.copyProject(project?.projectId, new Project({name: 
name, description: description, projectId: projectId}))
+    const onSubmit: SubmitHandler<Project> = (data) => {
+        if (operation === 'copy') {
+            KaravanApi.copyProject(data.projectId, project, after)
+        } else {
+            KaravanApi.postProject(data, after)
+        }
+    }
 
-        return action
-            .then(() => handleOnFormSubmitSuccess())
-            .catch((error) => handleOnFormSubmitFailure(error));
+    function after (result: boolean, res: AxiosResponse<Project> | any) {
+        if (result) {
+            onSuccess(res.projectId);
+        } else {
+            setBackendError(res?.response?.data);
+        }
     }
 
-    function handleOnFormSubmitSuccess () {
+    function onSuccess (projectId: string) {
         const message = operation !== "copy" ? "Project successfully created." 
: "Project successfully copied.";
-
         EventBus.sendAlert( "Success", message, "success");
         ProjectService.refreshProjectData(projectId);
         ProjectService.refreshProjects();
         setOperation("none");
-        resetForm();
-    }
-
-    function handleOnFormSubmitFailure(error: AxiosError) {
-        registerResponseErrors(error);
     }
 
     function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>): void {
-        if (event.key === 'Enter' && !isEmpty(name) && !isEmpty(description) 
&& !isEmpty(projectId)) {
-            handleFormSubmit();
+        if (event.key === 'Enter') {
+            handleSubmit(onSubmit)()
         }
     }
 
@@ -137,56 +100,33 @@ export function CreateProjectModal () {
             onClose={closeModal}
             onKeyDown={onKeyDown}
             actions={[
-                <Button key="confirm" variant="primary" 
onClick={handleSubmit(handleFormSubmit)}>Save</Button>,
+                <Button key="confirm" variant="primary"
+                        onClick={handleSubmit(onSubmit)}
+                        isDisabled={Object.getOwnPropertyNames(errors).length 
> 0}
+                >
+                    Save
+                </Button>,
                 <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
             ]}
             className="new-project"
         >
             <Form isHorizontal={true} autoComplete="off">
-                <FormGroup label="Name" fieldId="name" isRequired>
-                    <TextInput className="text-field" type="text" id="name"
-                               value={name}
-                               validated={!!errors.name ? 'error' : 'default'}
-                               {...register('name')}
-                               onChange={(e, v) => {
-                                   setName(v);
-                                   register('name').onChange(e);
-                               }}
-                    />
-                    {!!errors.name && <Text  style={{ color: 'red', fontStyle: 
'italic'}}>{errors?.name?.message}</Text>}
-                </FormGroup>
-                <FormGroup label="Description" fieldId="description" 
isRequired>
-                    <TextInput className="text-field" type="text" 
id="description"
-                               value={description}
-                               validated={!!errors.description ? 'error' : 
'default'}
-                               {...register('description')}
-                               onChange={(e, v) => {
-                                   setDescription(v);
-                                   register('description').onChange(e);
-                               }}
-                    />
-                    {!!errors.description && <Text  style={{ color: 'red', 
fontStyle: 'italic'}}>{errors?.description?.message}</Text>}
-                </FormGroup>
-                <FormGroup label="Project ID" fieldId="projectId" isRequired>
-                    <TextInput className="text-field" type="text" 
id="projectId"
-                               value={projectId}
-                               onFocus={e => setProjectId(projectId === '' ? 
CamelUi.nameFromTitle(name) : projectId)}
-                               validated={!!errors.projectId ? 'error' : 
'default'}
-                               {...register('projectId')}
-                               onChange={(e, v) => {
-                                   setProjectId(CamelUi.nameFromTitle(v));
-                                   register('projectId').onChange(e);
-                               }}
-                    />
-                    {!!errors.projectId && <Text  style={{ color: 'red', 
fontStyle: 'italic'}}>{errors?.projectId?.message}</Text>}
-                </FormGroup>
-                <Grid>
-                    {globalErrors &&
-                        globalErrors.map((error) => (
-                            <Alert title={error} key={error} 
variant="danger"></Alert>
-                        ))}
-                    <Divider role="presentation" />
-                </Grid>
+                {getTextField('name', 'Name', {
+                    length: v => v.length > 5 || 'Project name should be 
longer that 5 characters',
+                })}
+                {getTextField('description', 'Description', {
+                    length: v => v.length > 5 || 'Description name should be 
longer that 5 characters',
+                })}
+                {getTextField('projectId', 'ProjectID', {
+                    regex: v => isValidProjectId(v) || 'Only lowercase 
characters, numbers and dashes allowed',
+                    length: v => v.length > 5 || 'Project ID should be longer 
that 5 characters',
+                    name: v => !['templates', 'kamelets', 
'karavan'].includes(v) || "'templates', 'kamelets', 'karavan' can't be used as 
project",
+                })}
+                {backendError &&
+                    <FormAlert>
+                        <Alert variant="danger" title={backendError} 
aria-live="polite" isInline />
+                    </FormAlert>
+                }
             </Form>
         </Modal>
     )
diff --git a/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx 
b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
index ce80c9f2..3e10d7d0 100644
--- a/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
@@ -24,7 +24,6 @@ import {
 import '../designer/karavan.css';
 import {useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
-import ExclamationIcon from 
'@patternfly/react-icons/dist/esm/icons/exclamation-icon';
 
 export function DeleteProjectModal () {
 
diff --git a/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx 
b/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx
deleted file mode 100644
index 22f7b67a..00000000
--- a/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React, {useState} from 'react';
-import {
-    Alert,
-    Button,
-    Divider,
-    Form,
-    FormGroup,
-    Grid,
-    Modal,
-    ModalVariant,
-    Text,
-    TextInput
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {useProjectStore} from "../api/ProjectStore";
-import {ProjectService} from "../api/ProjectService";
-import {Project} from "../api/ProjectModels";
-import {CamelUi} from "../designer/utils/CamelUi";
-import {EventBus} from "../designer/utils/EventBus";
-import {useResponseErrorHandler} from 
"../shared/error/UseResponseErrorHandler";
-import {useForm} from "react-hook-form";
-import * as yup from 'yup';
-import {yupResolver} from '@hookform/resolvers/yup';
-import {AxiosError} from "axios";
-
-
-export function CreateServiceModal () {
-
-    const formValidationSchema = yup.object().shape({
-        name: yup
-            .string()
-            .required("Project name is required"),
-        description: yup
-            .string()
-            .required("Project description is required"),
-        projectId: yup
-            .string()
-            .required("Project ID is required")
-            .notOneOf(['templates', 'kamelets'], "'templates' or 'kamelets' 
can't be used as project ID")
-    });
-
-    const defaultFormValues = {
-        name: "",
-        description: "",
-        projectId: ""
-    };
-
-    const responseToFormErrorFields = new Map<string, string>([
-        ["projectId", "projectId"],
-        ["name", "name"],
-        ["description", "description"]
-    ]);
-
-    const {
-        register,
-        setError,
-        handleSubmit,
-        formState: { errors },
-        reset
-    } = useForm({
-        resolver: yupResolver(formValidationSchema),
-        mode: "onChange",
-        defaultValues: defaultFormValues
-    });
-
-    const {project, operation, setOperation} = useProjectStore();
-    const [name, setName] = useState('');
-    const [description, setDescription] = useState('');
-    const [runtime, setRuntime] = useState('');
-    const [projectId, setProjectId] = useState('');
-    const [globalErrors, registerResponseErrors, resetGlobalErrors] = 
useResponseErrorHandler(
-        responseToFormErrorFields,
-        setError
-    );
-
-    function resetForm() {
-        resetGlobalErrors();
-        reset(defaultFormValues);
-    }
-
-    function closeModal() {
-        setOperation("none");
-        resetForm();
-    }
-
-    function handleFormSubmit() {
-        const action = operation !== "copy" ?
-            ProjectService.createProject(new Project({name: name, description: 
description, projectId: projectId})) :
-            ProjectService.copyProject(project?.projectId, new Project({name: 
name, description: description, projectId: projectId}))
-
-        return action
-            .then(() => handleOnFormSubmitSuccess())
-            .catch((error) => handleOnFormSubmitFailure(error));
-    }
-
-    function handleOnFormSubmitSuccess () {
-        const message = operation !== "copy" ? "Project successfully created." 
: "Project successfully copied.";
-
-        EventBus.sendAlert( "Success", message, "success");
-        ProjectService.refreshProjectData(projectId);
-        ProjectService.refreshProjects();
-        setOperation("none");
-        resetForm();
-    }
-
-    function handleOnFormSubmitFailure(error: AxiosError) {
-        registerResponseErrors(error);
-    }
-
-    function onKeyDown (event: React.KeyboardEvent<HTMLDivElement>): void {
-        if (event.key === 'Enter' && name !== undefined && description !== 
undefined && projectId !== undefined) {
-            handleFormSubmit();
-        }
-    }
-
-    return (
-        <Modal
-            title={operation !== 'copy' ? "Create new project" : "Copy project 
from " + project?.projectId}
-            variant={ModalVariant.small}
-            isOpen={["create", "copy"].includes(operation)}
-            onClose={closeModal}
-            onKeyDown={onKeyDown}
-            actions={[
-                <Button key="confirm" variant="primary" 
onClick={handleSubmit(handleFormSubmit)}>Save</Button>,
-                <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
-            ]}
-            className="new-project"
-        >
-            <Form isHorizontal={true} autoComplete="off">
-                <FormGroup label="Name" fieldId="name" isRequired>
-                    <TextInput className="text-field" type="text" id="name"
-                               value={name}
-                               validated={!!errors.name ? 'error' : 'default'}
-                               {...register('name')}
-                               onChange={(e, v) => {
-                                   setName(v);
-                                   register('name').onChange(e);
-                               }}
-                    />
-                    {!!errors.name && <Text  style={{ color: 'red', fontStyle: 
'italic'}}>{errors?.name?.message}</Text>}
-                </FormGroup>
-                <FormGroup label="Description" fieldId="description" 
isRequired>
-                    <TextInput className="text-field" type="text" 
id="description"
-                               value={description}
-                               validated={!!errors.description ? 'error' : 
'default'}
-                               {...register('description')}
-                               onChange={(e, v) => {
-                                   setDescription(v);
-                                   register('description').onChange(e);
-                               }}
-                    />
-                    {!!errors.description && <Text  style={{ color: 'red', 
fontStyle: 'italic'}}>{errors?.description?.message}</Text>}
-                </FormGroup>
-                <FormGroup label="Project ID" fieldId="projectId" isRequired>
-                    <TextInput className="text-field" type="text" 
id="projectId"
-                               value={projectId}
-                               onFocus={e => setProjectId(projectId === '' ? 
CamelUi.nameFromTitle(name) : projectId)}
-                               validated={!!errors.projectId ? 'error' : 
'default'}
-                               {...register('projectId')}
-                               onChange={(e, v) => {
-                                   setProjectId(CamelUi.nameFromTitle(v));
-                                   register('projectId').onChange(e);
-                               }}
-                    />
-                    {!!errors.projectId && <Text  style={{ color: 'red', 
fontStyle: 'italic'}}>{errors?.projectId?.message}</Text>}
-                </FormGroup>
-                <Grid>
-                    {globalErrors &&
-                        globalErrors.map((error) => (
-                            <Alert title={error} key={error} 
variant="danger"></Alert>
-                        ))}
-                    <Divider role="presentation" />
-                </Grid>
-            </Form>
-        </Modal>
-    )
-}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/services/DeleteServiceModal.tsx 
b/karavan-app/src/main/webui/src/services/DeleteServiceModal.tsx
deleted file mode 100644
index 458fabfe..00000000
--- a/karavan-app/src/main/webui/src/services/DeleteServiceModal.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React from 'react';
-import {
-    Button,
-    Modal,
-    ModalVariant,
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {useProjectStore} from "../api/ProjectStore";
-import {ProjectService} from "../api/ProjectService";
-
-export function DeleteServiceModal () {
-
-    const {project, operation} = useProjectStore();
-
-    function closeModal () {
-        useProjectStore.setState({operation: "none"})
-    }
-
-    function confirmAndCloseModal () {
-        ProjectService.deleteProject(project);
-        useProjectStore.setState({operation: "none"});
-    }
-
-    const isOpen= operation === "delete";
-    return (
-            <Modal
-                title="Confirmation"
-                variant={ModalVariant.small}
-                isOpen={isOpen}
-                onClose={() => closeModal()}
-                actions={[
-                    <Button key="confirm" variant="primary" onClick={e => 
confirmAndCloseModal()}>Delete</Button>,
-                    <Button key="cancel" variant="link"
-                            onClick={e => closeModal()}>Cancel</Button>
-                ]}
-                onEscapePress={e => closeModal()}>
-                <div>{"Are you sure you want to delete the project " + 
project?.projectId + "?"}</div>
-            </Modal>
-            // }
-            // {(this.state.isProjectDeploymentModalOpen === true) && <Modal
-            //     variant={ModalVariant.small}
-            //     isOpen={this.state.isProjectDeploymentModalOpen}
-            //     onClose={() => this.setState({ 
isProjectDeploymentModalOpen: false })}
-            //     onEscapePress={e => this.setState({ 
isProjectDeploymentModalOpen: false })}>
-            //     <div>
-            //         <Alert key={this.state.projectToDelete?.projectId} 
className="main-alert" variant="warning"
-            //                title={"Deployment is Running!!"} 
isInline={true} isPlain={true}>
-            //             {"Delete the deployment (" + 
this.state.projectToDelete?.projectId + ")" + " first."}
-            //         </Alert>
-            //     </div>
-            // </Modal>
-    )
-}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/services/ServicesPage.tsx 
b/karavan-app/src/main/webui/src/services/ServicesPage.tsx
index 28f09a92..48a082a5 100644
--- a/karavan-app/src/main/webui/src/services/ServicesPage.tsx
+++ b/karavan-app/src/main/webui/src/services/ServicesPage.tsx
@@ -44,11 +44,9 @@ import {
 } from '@patternfly/react-table/deprecated';
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ServicesTableRow} from "./ServicesTableRow";
-import {DeleteServiceModal} from "./DeleteServiceModal";
-import {CreateServiceModal} from "./CreateServiceModal";
-import {useProjectStore, useStatusesStore} from "../api/ProjectStore";
+import {useStatusesStore} from "../api/ProjectStore";
 import {MainToolbar} from "../designer/MainToolbar";
-import {Project, ProjectType} from "../api/ProjectModels";
+import {ProjectType} from "../api/ProjectModels";
 import {KaravanApi} from "../api/KaravanApi";
 import {DockerComposeService, DockerCompose, ServicesYaml} from 
"../api/ServiceModels";
 import {shallow} from "zustand/shallow";
@@ -60,7 +58,6 @@ export function ServicesPage () {
 
     const [services, setServices] = useState<DockerCompose>();
     const [containers] = useStatusesStore((state) => [state.containers, 
state.setContainers], shallow);
-    const [operation] = useState<'create' | 'delete' | 'none'>('none');
     const [loading] = useState<boolean>(false);
 
     useEffect(() => {
@@ -87,12 +84,6 @@ export function ServicesPage () {
                 <ToolbarItem>
                     <Button variant="link" icon={<RefreshIcon/>} onClick={e => 
getServices()}/>
                 </ToolbarItem>
-                <ToolbarItem>
-                    <Button className="dev-action-button" icon={<PlusIcon/>}
-                            onClick={e =>
-                                useProjectStore.setState({operation: "create", 
project: new Project()})}
-                    >Create</Button>
-                </ToolbarItem>
             </ToolbarContent>
         </Toolbar>
     }
@@ -155,8 +146,6 @@ export function ServicesPage () {
             <PageSection isFilled className="kamelets-page">
                 {getServicesTable()}
             </PageSection>
-            {["create"].includes(operation) && <CreateServiceModal/>}
-            {["delete"].includes(operation) && <DeleteServiceModal/>}
             <ProjectLogPanel/>
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/util/CodeUtils.ts 
b/karavan-app/src/main/webui/src/util/CodeUtils.ts
index 69f6fcfa..6df49034 100644
--- a/karavan-app/src/main/webui/src/util/CodeUtils.ts
+++ b/karavan-app/src/main/webui/src/util/CodeUtils.ts
@@ -1,8 +1,10 @@
 import {ProjectFile} from "../api/ProjectModels";
 import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {Integration, KameletTypes, MetadataLabels} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {CamelUi} from "../designer/utils/CamelUi";
+import {KameletApi} from "karavan-core/lib/api/KameletApi";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 
 export class CodeUtils {
 
@@ -43,4 +45,29 @@ export class CodeUtils {
         const file = files.filter(f => f.name === 
'application.properties')?.at(0);
         return file?.code;
     }
+
+    static getCodeForNewFile(fileName: string, type: string, copyFromKamelet?: 
string): string {
+        if (type === 'INTEGRATION') {
+            return 
CamelDefinitionYaml.integrationToYaml(Integration.createNew(fileName, 'plain'));
+        } else if (type === 'KAMELET') {
+            const type: string | undefined = fileName.replace('.kamelet.yaml', 
'').split('-').pop();
+            const kameletType: KameletTypes | undefined = (type === "sink" || 
type === "source" || type === "action") ? type : undefined;
+            const integration = Integration.createNew(fileName, 'kamelet');
+            const meta: MetadataLabels = new 
MetadataLabels({"camel.apache.org/kamelet.type": kameletType});
+            integration.metadata.labels = meta;
+            if (copyFromKamelet !== undefined && copyFromKamelet !== '') {
+                const kamelet= KameletApi.getKamelets().filter(k => 
k.metadata.name === copyFromKamelet).at(0);
+                if (kamelet) {
+                    (integration as any).spec = kamelet.spec;
+                    (integration as any).metadata.labels = 
kamelet.metadata.labels;
+                    (integration as any).metadata.annotations = 
kamelet.metadata.annotations;
+                    const i = CamelUtil.cloneIntegration(integration);
+                    return CamelDefinitionYaml.integrationToYaml(i);
+                }
+            }
+            return CamelDefinitionYaml.integrationToYaml(integration);
+        } else {
+            return '';
+        }
+    }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/util/StringUtils.ts 
b/karavan-app/src/main/webui/src/util/StringUtils.ts
index 2a149781..0c32809b 100644
--- a/karavan-app/src/main/webui/src/util/StringUtils.ts
+++ b/karavan-app/src/main/webui/src/util/StringUtils.ts
@@ -1,3 +1,13 @@
 export function isEmpty(str: string) {
     return !str?.trim();
 }
+
+export function isValidFileName(input: string): boolean {
+    const pattern =/^[a-zA-Z0-9._-]+$/;
+    return pattern.test(input);
+}
+
+export function isValidProjectId(input: string): boolean {
+    const pattern = /^[a-z][a-z0-9-]*$/;
+    return pattern.test(input);
+}
diff --git a/karavan-app/src/main/webui/src/util/form-util.css 
b/karavan-app/src/main/webui/src/util/form-util.css
index 47c4819f..a5755b2a 100644
--- a/karavan-app/src/main/webui/src/util/form-util.css
+++ b/karavan-app/src/main/webui/src/util/form-util.css
@@ -9,4 +9,11 @@
 
 .pf-v5-c-modal-box .text-field-with-prefix 
.pf-v5-c-text-input-group__text-input {
     padding-left: 0;
-}
\ No newline at end of file
+}
+
+.pf-v5-c-modal-box .text-field-with-suffix .text-field-suffix {
+    margin-top: auto;
+    margin-bottom: auto;
+    padding-left: 3px;
+    padding-right: 3px;
+}
diff --git a/karavan-app/src/main/webui/src/util/useFormUtil.tsx 
b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
index fb7903d8..6e14da3e 100644
--- a/karavan-app/src/main/webui/src/util/useFormUtil.tsx
+++ b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import {FieldError, UseFormReturn} from "react-hook-form";
+import {Controller, FieldError, UseFormReturn} from "react-hook-form";
 import {
     Flex,
     FormGroup,
@@ -54,7 +54,6 @@ export function useFormUtil(formContext: UseFormReturn<any>) {
                 <TextInputGroup>
                     <TextInputGroupMain className="text-field-with-prefix" 
type="text" id={fieldName}
                                         value={getValues()[fieldName]}
-                        // validated={!!errors[fieldName] ? 'error' : 
'default'}
                                         {...register(fieldName, {required: 
(required ? "Required field" : false), validate: validate})}
                                         onChange={(e, v) => {
                                             setValue(fieldName, v, 
{shouldValidate: true});
@@ -68,6 +67,28 @@ export function useFormUtil(formContext: UseFormReturn<any>) 
{
         )
     }
 
+    function getTextFieldSuffix(fieldName: string, label: string, suffix: 
string,
+                                required: boolean,
+                                validate?: ((value: string, formValues: any) 
=> boolean | string) | Record<string, (value: string, formValues: any) => 
boolean | string>) {
+        const {setValue, getValues, register, formState: {errors}} = 
formContext;
+        return (
+            <FormGroup label={label} fieldId={fieldName} isRequired>
+                <TextInputGroup className="text-field-with-suffix">
+                    <TextInputGroupMain type="text" id={fieldName}
+                                        value={getValues()[fieldName]}
+                                        {...register(fieldName, {required: 
(required ? "Required field" : false), validate: validate})}
+                                        onChange={(e, v) => {
+                                            setValue(fieldName, v, 
{shouldValidate: true});
+                                        }}
+                    >
+                    </TextInputGroupMain>
+                    <Text className='text-field-suffix' 
component={TextVariants.p}>{suffix}</Text>
+                </TextInputGroup>
+                {getHelper((errors as any)[fieldName])}
+            </FormGroup>
+        )
+    }
+
     function getFormSelect(fieldName: string, label: string, options: [string, 
string][]) {
         const {register, watch, setValue, formState: {errors}} = formContext;
         return (
@@ -125,6 +146,6 @@ export function useFormUtil(formContext: 
UseFormReturn<any>) {
         )
     }
 
-    return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix}
+    return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix, 
getTextFieldSuffix}
 }
 

Reply via email to