838 lines
32 KiB
React
838 lines
32 KiB
React
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";
|
|
import Dropzone from "./Dropzone.jsx";
|
|
|
|
const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud, startOpenApi = false, prefillOpenApiData = "" }) => {
|
|
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()
|
|
|
|
useEffect(() => {
|
|
if (open && (startOpenApi || (prefillOpenApiData && prefillOpenApiData.length > 0))) {
|
|
if (prefillOpenApiData && prefillOpenApiData.length > 0) {
|
|
setOpenApiData(prefillOpenApiData)
|
|
setIsDropzone(true)
|
|
}
|
|
setOpenApiModal(true)
|
|
}
|
|
}, [open, startOpenApi, prefillOpenApiData])
|
|
|
|
// 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: "30px 20px 20px",
|
|
margin: 12,
|
|
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(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1" : "none",
|
|
transition: 'all 0.2s ease-in-out',
|
|
paddingBottom: isCloud ? 0 : 175,
|
|
}
|
|
|
|
return (
|
|
<Paper
|
|
elevation={hover ? 4 : 1}
|
|
onMouseEnter={() => setHover(true)}
|
|
onMouseLeave={() => setHover(false)}
|
|
onClick={func}
|
|
style={parsedStyle}
|
|
>
|
|
{icon}
|
|
<Typography sx={{ mt: 2 }}>{text}</Typography>
|
|
</Paper>
|
|
)
|
|
}
|
|
|
|
|
|
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 === true) {
|
|
setAppValidation(responseJson?.id)
|
|
navigate(`/apps/new?id=${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 ? <CircularProgress color="primary" /> : null
|
|
const errorText = openApiError?.length > 0 ? <div style={{ marginTop: 15, color: '#fd4c62' }}>Error: {openApiError}</div> : null
|
|
|
|
// Common dialog styles
|
|
const dialogStyle = {
|
|
borderRadius: 2,
|
|
border: theme.palette.DialogStyle.border,
|
|
minWidth: '500px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
backgroundColor: theme.palette.DialogStyle.backgroundColor,
|
|
zIndex: 1000,
|
|
'& .MuiDialogContent-root': {
|
|
backgroundColor: theme.palette.DialogStyle.backgroundColor,
|
|
padding: '24px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
},
|
|
'& .MuiDialogTitle-root': {
|
|
backgroundColor: theme.palette.DialogStyle.backgroundColor,
|
|
padding: '24px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
},
|
|
'& .MuiDialogActions-root': {
|
|
backgroundColor: theme.palette.DialogStyle.backgroundColor,
|
|
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 */}
|
|
<Dialog
|
|
TransitionProps={{
|
|
timeout: 200,
|
|
}}
|
|
open={open}
|
|
onClose={onClose}
|
|
PaperProps={{
|
|
sx: dialogStyle
|
|
}}
|
|
>
|
|
<DialogTitle sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
pb: 3,
|
|
pt: 3,
|
|
pl: 4,
|
|
pr: 3,
|
|
}}>
|
|
<Typography variant="h5"color="textPrimary" sx={{ fontWeight: 500, }}>
|
|
Create New App
|
|
</Typography>
|
|
<IconButton
|
|
onClick={() => {
|
|
setOpenApi("")
|
|
setOpenApiError("")
|
|
setAppValidation("")
|
|
setValidation(false)
|
|
onClose()
|
|
}}
|
|
sx={{
|
|
color: theme.palette.text.primary,
|
|
'&:hover': { bgcolor: theme.palette.hoverColor },
|
|
}}
|
|
>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</DialogTitle>
|
|
<DialogContent sx={{ p: 3 }}>
|
|
<div style={{ display: "flex", gap: 16, }}>
|
|
<AppCreateButton
|
|
text="Upload OpenAPI or Swagger"
|
|
func={() => {
|
|
setOpenApiModal(true)
|
|
onClose()
|
|
}}
|
|
icon={<PublishIcon style={{ minHeight: 50, maxHeight: 50 }} />}
|
|
/>
|
|
<AppCreateButton
|
|
text="Generate from Documentation"
|
|
func={() => {
|
|
setGenerateAppModal(true)
|
|
onClose()
|
|
}}
|
|
icon={<AutoFixHighIcon style={{ minHeight: 50, maxHeight: 50 }} />}
|
|
/>
|
|
</div>
|
|
<Button
|
|
variant="outlined"
|
|
component="label"
|
|
color="secondary"
|
|
fullWidth
|
|
onClick={() => {
|
|
navigate('/apps/new')
|
|
onClose()
|
|
}}
|
|
sx={{ mt: 3, mb: 1 }}
|
|
>
|
|
Create from scratch
|
|
</Button>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* OpenAPI Modal */}
|
|
<Dialog
|
|
TransitionProps={{ timeout: 200 }}
|
|
open={openApiModal}
|
|
onClose={() => {
|
|
setOpenApiModal(false)
|
|
setGenerateAppModal(false)
|
|
}}
|
|
PaperProps={{
|
|
sx: {
|
|
...dialogStyle,
|
|
bgcolor: '#1A1A1A',
|
|
maxWidth: '800px',
|
|
width: '100%'
|
|
}
|
|
}}
|
|
>
|
|
<Dropzone onDrop={uploadFile} style={{ width: '100%' }}>
|
|
<DialogTitle sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
pb: 3,
|
|
pt: 3,
|
|
px: 4,
|
|
}}>
|
|
<Typography variant="h6" sx={{
|
|
color: theme.palette.text.primary,
|
|
fontWeight: 500,
|
|
fontFamily: theme?.typography?.fontFamily
|
|
}}>
|
|
Create New App from Open API or Swagger
|
|
</Typography>
|
|
<IconButton
|
|
onClick={() => {
|
|
setOpenApiModal(false)
|
|
setGenerateAppModal(false)
|
|
setOpenApi("")
|
|
setOpenApiError("")
|
|
setAppValidation("")
|
|
setValidation(false)
|
|
}}
|
|
sx={{
|
|
color: 'rgba(255,255,255,0.7)',
|
|
'&:hover': { bgcolor: 'rgba(255,255,255,0.1)' }
|
|
}}
|
|
>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</DialogTitle>
|
|
<DialogContent sx={{ px: 4, py: 3 }}>
|
|
<div style={{ display: "flex", fontSize: '14px', gap: '5px', alignItems: 'center', marginBottom: '10px', fontFamily: theme?.typography?.fontFamily, marginTop: '15px' }}>
|
|
<Typography sx={{ color: theme.palette.text.primary, fontSize: '16px' }}>
|
|
Paste in the URI for the OpenAPI or find out
|
|
</Typography>
|
|
<Link
|
|
to="https://shuffler.io/docs/apps#getting-started"
|
|
style={{
|
|
color: '#ff8544',
|
|
textDecoration: 'none',
|
|
textDecoration: 'underline',
|
|
fontSize: '16px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
textUnderlineOffset: "3px",
|
|
}}>
|
|
How to find URI for openAPI?
|
|
</Link>
|
|
</div>
|
|
|
|
|
|
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
<TextField
|
|
fullWidth
|
|
variant="outlined"
|
|
placeholder="Open API URI"
|
|
style={{ fontFamily: theme?.typography?.fontFamily, fontSize: '16px' }}
|
|
sx={{
|
|
'& .MuiOutlinedInput-root': {
|
|
height: '40px',
|
|
},
|
|
'& .MuiOutlinedInput-input::placeholder': {
|
|
fontSize: '16px'
|
|
}
|
|
}}
|
|
onChange={(e) => setOpenApi(e.target.value)}
|
|
/>
|
|
<Button
|
|
sx={{
|
|
color: '#2bc07e',
|
|
border: "1px solid #2bc07e",
|
|
borderRadius: '4px',
|
|
width: '200px',
|
|
py: 1,
|
|
px: 3,
|
|
textTransform: 'none',
|
|
height: '40px',
|
|
fontSize: '16px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
cursor: 'pointer'
|
|
}}
|
|
disabled={openApi.length === 0 || appValidation.length > 0}
|
|
onClick={() => {
|
|
setOpenApiError("");
|
|
validateRemote();
|
|
}}
|
|
>
|
|
Validate
|
|
</Button>
|
|
</div>
|
|
|
|
<Typography
|
|
variant="body2"
|
|
sx={{
|
|
color: 'rgba(255,255,255,0.5)',
|
|
mb: 3,
|
|
mt: 1,
|
|
fontSize: '14px',
|
|
fontFamily: theme?.typography?.fontFamily
|
|
}}>
|
|
Must point to a version 2 or 3 OpenAPI specification.
|
|
</Typography>
|
|
|
|
<Typography sx={{ mb: 1.5, mt: 2, color: 'rgba(255,255,255,0.7)', fontSize: '14px', fontFamily: theme?.typography?.fontFamily }}>
|
|
Or upload a YAML or JSON specification
|
|
</Typography>
|
|
<div style={{
|
|
width: '100%',
|
|
boxSizing: 'border-box',
|
|
border: '1px dashed rgba(255,255,255,0.35)',
|
|
borderRadius: 4,
|
|
padding: 20,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: '#1a1a1a',
|
|
marginTop: '12px',
|
|
cursor: 'pointer'
|
|
}}
|
|
onClick={() => upload.current.click()}
|
|
>
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
<Typography sx={{ color: 'rgba(255,255,255,0.9)', fontSize: '15px', fontFamily: theme?.typography?.fontFamily }}>
|
|
Drag & drop your OpenAPI (YAML/JSON) anywhere
|
|
</Typography>
|
|
<Typography sx={{ color: 'rgba(255,255,255,0.6)', fontSize: '13px', mt: 0.5, fontFamily: theme?.typography?.fontFamily }}>
|
|
or click to browse files
|
|
</Typography>
|
|
</div>
|
|
<Button
|
|
variant="outlined"
|
|
onClick={(e) => { e.stopPropagation(); upload.current.click(); }}
|
|
sx={{
|
|
color: '#FF8544',
|
|
borderColor: '#FF8544',
|
|
px: 3,
|
|
py: 0.75,
|
|
'&:hover': {
|
|
borderColor: '#FF8544',
|
|
color: '#FF8544',
|
|
bgcolor: 'rgba(255,133,68,0.1)'
|
|
},
|
|
textTransform: 'none',
|
|
fontSize: '14px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
height: '36px'
|
|
}}
|
|
>
|
|
Upload
|
|
</Button>
|
|
</div>
|
|
|
|
<input
|
|
hidden
|
|
type="file"
|
|
ref={upload}
|
|
accept="application/JSON,application/YAML,application/yaml,text/yaml,text/x-yaml,application/x-yaml,application/vnd.yaml,.yml,.yaml"
|
|
multiple={false}
|
|
onChange={uploadFile}
|
|
/>
|
|
{errorText}
|
|
</DialogContent>
|
|
<DialogActions sx={{
|
|
py: 2,
|
|
px: 3,
|
|
gap: 2,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center'
|
|
}}>
|
|
{circularLoader}
|
|
<Button
|
|
variant="contained"
|
|
sx={{
|
|
bgcolor: '#FF8544',
|
|
color: '#000',
|
|
'&:hover': {
|
|
bgcolor: '#ff7a33'
|
|
},
|
|
textTransform: 'none',
|
|
py: 1.5,
|
|
mb: 1.5,
|
|
fontSize: '16px',
|
|
fontWeight: 500,
|
|
width: '100%',
|
|
maxWidth: '300px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
cursor: 'pointer',
|
|
height: '40px'
|
|
}}
|
|
disabled={appValidation.length === 0}
|
|
onClick={redirectOpenApi}
|
|
>
|
|
Continue
|
|
</Button>
|
|
</DialogActions>
|
|
</Dropzone>
|
|
</Dialog>
|
|
|
|
{/* Generate App Modal */}
|
|
<Dialog
|
|
TransitionProps={{ timeout: 200 }}
|
|
open={generateAppModal}
|
|
onClose={() => {
|
|
setGenerateAppModal(false)
|
|
setOpenApiModal(false)
|
|
}}
|
|
PaperProps={{
|
|
sx: {
|
|
...dialogStyle,
|
|
bgcolor: '#1A1A1A',
|
|
maxWidth: '610px',
|
|
width: '100%',
|
|
}
|
|
}}
|
|
>
|
|
<DialogTitle sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
pt: 7,
|
|
px: 4,
|
|
}}>
|
|
<Typography variant="h6" sx={{
|
|
color: theme.palette.text.primary,
|
|
fontWeight: 500,
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
}}>
|
|
Generate an app based on documentation (beta)
|
|
</Typography>
|
|
<IconButton
|
|
onClick={() => {
|
|
setGenerateAppModal(false)
|
|
setOpenApiModal(false)
|
|
setOpenApi("")
|
|
setOpenApiError("")
|
|
setAppValidation("")
|
|
setValidation(false)
|
|
}}
|
|
sx={{
|
|
color: theme.palette.text.primary,
|
|
'&:hover': { bgcolor: theme.palette.hoverColor }
|
|
}}
|
|
>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</DialogTitle>
|
|
<DialogContent sx={{ px: 4, py: 3, pt: 0 }}>
|
|
<Typography sx={{
|
|
color: theme.palette.text.primary,
|
|
mb: 2,
|
|
fontSize: '14px',
|
|
mt: 2,
|
|
fontFamily: theme?.typography?.fontFamily
|
|
}}>
|
|
Paste in a URL, and we will make it into an app for you.
|
|
<b>{isCloud ? "" : " Uses Shuffle Cloud (https://shuffler.io) for processing (for now)."}</b>
|
|
</Typography>
|
|
|
|
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
<TextField
|
|
fullWidth
|
|
variant="outlined"
|
|
placeholder="API Documentation URL"
|
|
sx={{
|
|
bgcolor: theme.palette.textFieldStyle.backgroundColor,
|
|
'& .MuiOutlinedInput-root': {
|
|
height: '40px',
|
|
color: theme.palette.text.primary,
|
|
'& fieldset': {
|
|
borderWidth: '1px',
|
|
borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1",
|
|
},
|
|
'&:hover fieldset': {
|
|
borderWidth: '1px',
|
|
borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1",
|
|
},
|
|
'&.Mui-focused fieldset': {
|
|
borderWidth: '2px',
|
|
borderImage: "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1",
|
|
}
|
|
},
|
|
'& .MuiOutlinedInput-input::placeholder': {
|
|
fontSize: '14px'
|
|
}
|
|
}}
|
|
style={{ fontFamily: theme?.typography?.fontFamily, borderRadius: "4px" }}
|
|
onChange={(e) => {
|
|
setOpenApi(e.target.value);
|
|
}} />
|
|
</div>
|
|
|
|
<Typography
|
|
variant="body2"
|
|
sx={{
|
|
color: 'rgba(255,255,255,0.5)',
|
|
mt: 1,
|
|
fontSize: '14px',
|
|
fontFamily: theme?.typography?.fontFamily
|
|
}}>
|
|
Should be a documentation page containing an API.
|
|
</Typography>
|
|
{errorText}
|
|
</DialogContent>
|
|
<DialogActions sx={{
|
|
py: 2,
|
|
pt: 1,
|
|
px: 3,
|
|
gap: 2,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center'
|
|
}}>
|
|
{circularLoader}
|
|
{
|
|
!validation &&
|
|
<Typography color="textSecondary" sx={{ fontSize: '14px', fontFamily: theme?.typography?.fontFamily, }}>
|
|
This may take multiple minutes based on the size of the documentation.
|
|
</Typography>
|
|
}
|
|
<Button
|
|
variant="contained"
|
|
sx={{
|
|
bgcolor: '#FF8544',
|
|
color: '#000',
|
|
'&:hover': {
|
|
bgcolor: '#ff7a33'
|
|
},
|
|
textTransform: 'none',
|
|
py: 1.5,
|
|
mb: 1,
|
|
fontSize: '14px',
|
|
fontWeight: 500,
|
|
width: '100%',
|
|
borderRadius: '4px',
|
|
maxWidth: '300px',
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
cursor: 'pointer',
|
|
height: '40px'
|
|
}}
|
|
disabled={openApi.length === 0 || appValidation.length > 0 || validation}
|
|
onClick={() => {
|
|
setOpenApiError("");
|
|
validateDocumentationUrl();
|
|
}}
|
|
>
|
|
Generate
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default AppCreationModal
|