diff --git a/frontend/src/components/AppCreationModal.jsx b/frontend/src/components/AppCreationModal.jsx
new file mode 100644
index 00000000..7b650fbd
--- /dev/null
+++ b/frontend/src/components/AppCreationModal.jsx
@@ -0,0 +1,777 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { useNavigate, Link } from 'react-router-dom';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ Typography,
+ Paper,
+ Button,
+ FormControl,
+ TextField,
+ DialogActions,
+ IconButton,
+ CircularProgress
+} from '@mui/material'
+
+// Icons
+import CloseIcon from '@mui/icons-material/Close'
+import PublishIcon from '@mui/icons-material/Publish'
+import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
+import CreateIcon from '@mui/icons-material/Create'
+import { toast } from 'react-toastify'
+import YAML from "yaml";
+
+
+const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
+ const [openApiModal, setOpenApiModal] = useState(false)
+ const [generateAppModal, setGenerateAppModal] = useState(false)
+ const [openApi, setOpenApi] = useState("")
+ const [openApiData, setOpenApiData] = useState("")
+ const [openApiError, setOpenApiError] = useState("")
+ const [validation, setValidation] = useState(false)
+ const [appValidation, setAppValidation] = useState("")
+ const [isDropzone, setIsDropzone] = useState(false)
+
+ const navigate = useNavigate()
+ const upload = useRef()
+
+ // Style for the create options
+ const AppCreateButton = ({ text, func, icon }) => {
+ const [hover, setHover] = React.useState(false)
+ const makeFancy = text?.includes("Generate")
+
+ const parsedStyle = {
+ flex: 1,
+ padding: 20,
+ margin: 12,
+ paddingTop: 30,
+ backgroundColor: hover && !makeFancy ? theme.palette.surfaceColor : "transparent",
+ cursor: hover ? "pointer" : "default",
+ textAlign: "center",
+ minHeight: 180,
+ maxHeight: 180,
+ borderRadius: 8,
+ border: makeFancy
+ ? "1px solid transparent"
+ : hover
+ ? ""
+ : "1px solid rgba(255,255,255,0.3)",
+ borderImage: makeFancy ? "linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1" : "none",
+ transition: 'all 0.2s ease-in-out',
+ }
+
+ return (
+ setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ onClick={func}
+ style={parsedStyle}
+ >
+ {icon}
+ {text}
+
+ )
+ }
+
+
+ const uploadFile = (e) => {
+ const isDropzone =
+ e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
+ const files = isDropzone ? e.dataTransfer.files : e.target.files;
+
+ const reader = new FileReader();
+
+ try {
+ reader.addEventListener("load", (e) => {
+ const content = e.target.result;
+ setOpenApiData(content);
+ setIsDropzone(isDropzone);
+ setOpenApiModal(true);
+ });
+ } catch (e) {
+ console.log("Error in dropzone: ", e);
+ }
+
+ try {
+ reader.readAsText(files[0]);
+ } catch (error) {
+ toast("Failed to read file");
+ }
+ };
+
+ useEffect(() => {
+ if (openApiData.length > 0) {
+ setOpenApiError("");
+ validateOpenApi(openApiData);
+ }
+ }, [openApiData]);
+
+
+ // useEffect(() => {
+ // console.log("APPVALID: ", appValidation)
+ // redirectOpenApi()
+ // }, [appValidation])
+
+ useEffect(() => {
+ if (appValidation && isDropzone) {
+ redirectOpenApi();
+ setIsDropzone(false);
+ }
+ }, [appValidation, isDropzone]);
+
+ const validateDocumentationUrl = () => {
+ setValidation(true);
+
+ // curl https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi -d '{"url": "https://gitlab.com/rhab/PyOTRS/-/raw/main/pyotrs/lib.py?ref_type=heads"}' -H "Content-Type: application/json"
+ const urldata = {
+ "url": openApi,
+ }
+
+ //fetch("http://localhost:8080/doc_to_openapi", {
+ //fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi", {
+ fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/api/v1/doc_to_openapi", {
+ method: "POST",
+ headers: {
+ "Accept": "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(urldata),
+ })
+ .then((response) => {
+ setValidation(false);
+ if (response.status !== 200) {
+ toast("Error in generation: " + response.status);
+ setOpenApiError("Error in generation - bad status: " + response.status);
+ return response.text();
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // Check if openapi or swagger in string of the json
+ var parsedtext = responseJson
+ try {
+ parsedtext = JSON.stringify(responseJson);
+ if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) {
+ setValidation(false)
+ setOpenApiError("Error in generation: " + parsedtext)
+
+ return
+ }
+ } catch (e) {
+ setValidation(false);
+ setOpenApiError("Error in generation (2): " + e.toString());
+ return;
+ }
+
+ console.log("Validating response!");
+ validateOpenApi(parsedtext)
+ })
+ .catch((error) => {
+ setValidation(false);
+ toast(error.toString());
+ setOpenApiError(error.toString());
+ });
+ }
+
+ const validateRemote = () => {
+ setValidation(true);
+
+ fetch(globalUrl + "/api/v1/get_openapi_uri", {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ },
+ body: JSON.stringify(openApi),
+ credentials: "include",
+ })
+ .then((response) => {
+ setValidation(false);
+ if (response.status !== 200) {
+ return response.json();
+ }
+
+ return response.text();
+ })
+ .then((responseJson) => {
+ if (typeof responseJson !== "string" && !responseJson.success) {
+ console.log(responseJson.reason);
+ if (responseJson.reason !== undefined) {
+ setOpenApiError(responseJson.reason);
+ } else {
+ setOpenApiError("Undefined issue with OpenAPI validation");
+ }
+ return;
+ }
+
+ console.log("Validating response!");
+ validateOpenApi(responseJson);
+ })
+ .catch((error) => {
+ toast(error.toString());
+ setOpenApiError(error.toString());
+ });
+ };
+
+ const escapeApiData = (apidata) => {
+ //console.log(apidata)
+ try {
+ return JSON.stringify(JSON.parse(apidata));
+ } catch (error) {
+ console.log("JSON DECODE ERROR - TRY YAML");
+ }
+
+ try {
+ const parsed = YAML.parse(YAML.stringify(apidata));
+ //const parsed = YAML.parse(apidata))
+ console.log(YAML.stringify(parsed))
+ return YAML.stringify(parsed);
+ } catch (error) {
+ console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: " + error);
+ setOpenApiError("Local error: " + error.toString());
+ }
+
+ return "";
+ };
+
+
+ const validateOpenApi = (openApidata) => {
+ var newApidata = escapeApiData(openApidata);
+ if (newApidata === "") {
+ // Used to return here
+ newApidata = openApidata;
+ return;
+ }
+
+ //console.log(newApidata)
+
+ setValidation(true);
+ fetch(globalUrl + "/api/v1/validate_openapi", {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ },
+ body: openApidata,
+ credentials: "include",
+ })
+ .then((response) => {
+
+ setValidation(false);
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ setAppValidation(responseJson.id);
+ } else {
+ if (responseJson.reason !== undefined) {
+ setOpenApiError(responseJson.reason);
+ }
+ toast("An error occurred in the response");
+ }
+ })
+ .catch((error) => {
+ setValidation(false);
+ toast(error.toString());
+ setOpenApiError(error.toString());
+ });
+ };
+
+ const redirectOpenApi = () => {
+ if (appValidation === undefined || appValidation === null || appValidation.length === 0) {
+ return
+ }
+
+ toast.success("Successfully validated OpenAPI. Redirecting to app creation. Remember to save the app to be able to use it.", {
+ // Disable autoclose
+ autoClose: 10000,
+ })
+ navigate(`/apps/new?id=${appValidation}`)
+ }
+
+
+
+
+ // const validateOpenApi = (openApidata) => {
+
+ // var newApidata = escapeApiData(openApidata);
+ // if (newApidata === "") {
+ // // Used to return here
+ // newApidata = openApidata;
+ // return;
+ // }
+ // setValidation(true)
+ // const url = `${globalUrl}/api/v1/verify_openapi`
+
+ // fetch(url, {
+ // method: "POST",
+ // headers: {
+ // "Content-Type": "application/json",
+ // Accept: "application/json",
+ // },
+ // body: JSON.stringify(openApidata),
+ // credentials: "include",
+ // })
+ // .then((response) => response.json())
+ // .then((responseJson) => {
+ // if (responseJson.success === false) {
+ // setOpenApiError(responseJson.reason)
+ // setValidation(false)
+ // setAppValidation("")
+ // } else {
+ // setAppValidation(responseJson.id)
+ // setValidation(false)
+ // }
+ // })
+ // .catch((error) => {
+ // setOpenApiError("Failed loading: " + error.toString())
+ // setValidation(false)
+ // setAppValidation("")
+ // })
+ // }
+
+
+
+
+
+ // Validation and redirect functions
+
+
+
+ const circularLoader = validation ? : null
+ const errorText = openApiError?.length > 0 ?
Error: {openApiError}
: null
+
+ // Common dialog styles
+ const dialogStyle = {
+ borderRadius: 2,
+ border: "1px solid #494949",
+ minWidth: '500px',
+ fontFamily: theme?.typography?.fontFamily,
+ backgroundColor: "#1A1A1A",
+ zIndex: 1000,
+ '& .MuiDialogContent-root': {
+ backgroundColor: "#1A1A1A",
+ padding: '24px',
+ fontFamily: theme?.typography?.fontFamily,
+ },
+ '& .MuiDialogTitle-root': {
+ backgroundColor: "#1A1A1A",
+ padding: '24px',
+ fontFamily: theme?.typography?.fontFamily,
+ },
+ '& .MuiDialogActions-root': {
+ backgroundColor: "#1A1A1A",
+ padding: '16px 24px',
+ fontFamily: theme?.typography?.fontFamily,
+ },
+ '& .MuiTypography-root': {
+ fontFamily: theme?.typography?.fontFamily,
+ },
+ '& .MuiButton-root': {
+ fontFamily: theme?.typography?.fontFamily,
+ },
+ transition: 'all 0.2s ease-in-out',
+ };
+
+ return (
+ <>
+ {/* Main App Creation Modal */}
+
+
+ {/* OpenAPI Modal */}
+
+
+ {/* Generate App Modal */}
+
+ >
+ )
+}
+
+export default AppCreationModal