diff --git a/frontend/src/component/project/Project/Import/ImportLayoutContainer.tsx b/frontend/src/component/project/Project/Import/ImportLayoutContainer.tsx new file mode 100644 index 0000000000..be4f119e22 --- /dev/null +++ b/frontend/src/component/project/Project/Import/ImportLayoutContainer.tsx @@ -0,0 +1,10 @@ +import { styled } from '@mui/material'; + +export const ImportLayoutContainer = styled('div')(({ theme }) => ({ + backgroundColor: '#fff', + padding: theme.spacing(5, 8, 3, 8), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(3), + flexBasis: '70%', +})); diff --git a/frontend/src/component/project/Project/Import/ImportModal.tsx b/frontend/src/component/project/Project/Import/ImportModal.tsx index 63cfd50612..84a1264615 100644 --- a/frontend/src/component/project/Project/Import/ImportModal.tsx +++ b/frontend/src/component/project/Project/Import/ImportModal.tsx @@ -1,29 +1,11 @@ -import { - Button, - styled, - Tabs, - Tab, - TextField, - Box, - Typography, -} from '@mui/material'; +import { styled } from '@mui/material'; import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; -import { ArrowUpward } from '@mui/icons-material'; import React, { useState } from 'react'; -import { useImportApi } from 'hooks/api/actions/useImportApi/useImportApi'; -import { StyledFileDropZone } from './StyledFileDropZone'; -import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender'; -import useToast from 'hooks/useToast'; -import { ImportOptions } from './ImportOptions'; -import { ImportExplanation } from './ImportExplanation'; -import { PulsingAvatar } from './PulsingAvatar'; -import Timeline from '@mui/lab/Timeline'; -import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; -import TimelineSeparator from '@mui/lab/TimelineSeparator'; -import TimelineConnector from '@mui/lab/TimelineConnector'; -import TimelineContent from '@mui/lab/TimelineContent'; -import TimelineDot from '@mui/lab/TimelineDot'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ImportTimeline } from './ImportTimeline'; +import { ImportStage } from './ImportStage'; +import { ConfigurationStage } from './configure/ConfigurationStage'; +import { ValidationStage } from './validate/ValidationState'; const ModalContentContainer = styled('div')(({ theme }) => ({ minHeight: '100vh', @@ -45,70 +27,16 @@ const TimelineHeader = styled('div')(({ theme }) => ({ marginBottom: theme.spacing(4), })); -const ImportLayoutContainer = styled('div')(({ theme }) => ({ - backgroundColor: '#fff', - padding: theme.spacing(3, 8, 3, 8), - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(3), - flexBasis: '70%', -})); - -const StyledTextField = styled(TextField)(({ theme }) => ({ - width: '100%', -})); - -const DropMessage = styled(Typography)(({ theme }) => ({ - marginTop: theme.spacing(4), - fontSize: theme.fontSizes.mainHeader, -})); - -const SelectFileMessage = styled(Typography)(({ theme }) => ({ - marginTop: theme.spacing(2), - marginBottom: theme.spacing(1.5), - color: theme.palette.text.secondary, -})); - -const MaxSizeMessage = styled(Typography)(({ theme }) => ({ - marginTop: theme.spacing(4), - color: theme.palette.text.secondary, -})); - -const ActionsContainer = styled(Box)(({ theme }) => ({ - width: '100%', - borderTop: `1px solid ${theme.palette.dividerAlternative}`, - marginTop: 'auto', - paddingTop: theme.spacing(3), - display: 'flex', - justifyContent: 'flex-end', -})); - interface IImportModalProps { open: boolean; - setOpen: React.Dispatch>; - + setOpen: (value: boolean) => void; project: string; } -type ImportMode = 'file' | 'code'; - export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { - const { createImport } = useImportApi(); - - const [environment, setEnvironment] = useState(''); - const [importPayload, setImportPayload] = useState(''); - const [activeTab, setActiveTab] = useState('file'); - const [dragActive, setDragActive] = useState(false); - - const onSubmit = async () => { - await createImport({ - data: JSON.parse(importPayload), - environment, - project, - }); - }; - - const { setToastData } = useToast(); + const [importStage, setImportStage] = useState({ + name: 'configure', + }); return ( { Process - + - - - - setActiveTab('file')} - /> - setActiveTab('code')} - /> - - - setOpen(false)} + onSubmit={configuration => + setImportStage({ + name: 'validate', + ...configuration, + }) + } + /> + } + /> + {importStage.name === 'validate' ? ( + - { - setImportPayload(data); - setActiveTab('code'); - setToastData({ - type: 'success', - title: 'File uploaded', - }); - }} - onError={error => { - setToastData({ - type: 'error', - title: error, - }); - }} - onDragStatusChange={setDragActive} - > - - - - - {dragActive - ? 'Drop your file to upload' - : 'Drop your file here'} - - - or select a file from your device - - - - JSON format: max 500 kB - - - } - elseShow={ - - setImportPayload(event.target.value) - } - value={importPayload} - multiline - minRows={13} - maxRows={13} - /> - } - /> - - - - - + ) : ( + '' + )} ); diff --git a/frontend/src/component/project/Project/Import/ImportStage.ts b/frontend/src/component/project/Project/Import/ImportStage.ts new file mode 100644 index 0000000000..bf902b65f1 --- /dev/null +++ b/frontend/src/component/project/Project/Import/ImportStage.ts @@ -0,0 +1,4 @@ +export type ImportStage = + | { name: 'configure' } + | { name: 'validate'; environment: string; payload: string } + | { name: 'import' }; diff --git a/frontend/src/component/project/Project/Import/ImportTimeline.tsx b/frontend/src/component/project/Project/Import/ImportTimeline.tsx index ca95a7907a..322b8e8957 100644 --- a/frontend/src/component/project/Project/Import/ImportTimeline.tsx +++ b/frontend/src/component/project/Project/Import/ImportTimeline.tsx @@ -6,6 +6,7 @@ import TimelineConnector from '@mui/lab/TimelineConnector'; import TimelineDot from '@mui/lab/TimelineDot'; import TimelineContent from '@mui/lab/TimelineContent'; import Timeline from '@mui/lab/Timeline'; +import { ImportStage } from './ImportStage'; const StyledTimeline = styled(Timeline)(() => ({ [`& .${timelineItemClasses.root}:before`]: { @@ -54,7 +55,7 @@ const TimelineItemDescription = styled(Box)(({ theme }) => ({ })); export const ImportTimeline: FC<{ - stage: 'configure' | 'validate' | 'import'; + stage: ImportStage['name']; }> = ({ stage }) => { return ( diff --git a/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx b/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx new file mode 100644 index 0000000000..3aa4cf95e6 --- /dev/null +++ b/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx @@ -0,0 +1,174 @@ +import { + Box, + Button, + styled, + Tab, + Tabs, + TextField, + Typography, +} from '@mui/material'; +import { ImportOptions } from './ImportOptions'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { StyledFileDropZone } from './StyledFileDropZone'; +import { PulsingAvatar } from './PulsingAvatar'; +import { ArrowUpward } from '@mui/icons-material'; +import { ImportExplanation } from './ImportExplanation'; +import React, { FC, useState } from 'react'; +import useToast from 'hooks/useToast'; +import { ImportLayoutContainer } from '../ImportLayoutContainer'; + +const StyledTextField = styled(TextField)(({ theme }) => ({ + width: '100%', +})); + +const DropMessage = styled(Typography)(({ theme }) => ({ + marginTop: theme.spacing(4), + fontSize: theme.fontSizes.mainHeader, +})); + +const SelectFileMessage = styled(Typography)(({ theme }) => ({ + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1.5), + color: theme.palette.text.secondary, +})); + +const MaxSizeMessage = styled(Typography)(({ theme }) => ({ + marginTop: theme.spacing(4), + color: theme.palette.text.secondary, +})); + +const ActionsContainer = styled(Box)(({ theme }) => ({ + width: '100%', + borderTop: `1px solid ${theme.palette.dividerAlternative}`, + marginTop: 'auto', + paddingTop: theme.spacing(3), + display: 'flex', + justifyContent: 'flex-end', +})); + +type ImportMode = 'file' | 'code'; + +const isValidJSON = (json: string) => { + try { + JSON.parse(json); + return true; + } catch (e) { + return false; + } +}; + +interface IConfigurationSettings { + environment: string; + payload: string; +} + +export const ConfigurationStage: FC<{ + project: string; + onClose: () => void; + onSubmit: (props: IConfigurationSettings) => void; +}> = ({ project, onClose, onSubmit }) => { + const [environment, setEnvironment] = useState(''); + const [importPayload, setImportPayload] = useState(''); + const [activeTab, setActiveTab] = useState('file'); + const [dragActive, setDragActive] = useState(false); + const { setToastData } = useToast(); + + return ( + + + + setActiveTab('file')} + /> + setActiveTab('code')} + /> + + + + { + setImportPayload(data); + setActiveTab('code'); + setToastData({ + type: 'success', + title: 'File uploaded', + }); + }} + onError={error => { + setImportPayload(''); + setToastData({ + type: 'error', + title: error, + }); + }} + onDragStatusChange={setDragActive} + > + + + + + {dragActive + ? 'Drop your file to upload' + : 'Drop your file here'} + + + or select a file from your device + + + JSON format: max 500 kB + + } + elseShow={ + setImportPayload(event.target.value)} + value={importPayload} + multiline + minRows={13} + maxRows={13} + /> + } + /> + + + + + + + ); +}; diff --git a/frontend/src/component/project/Project/Import/FileDropZone.tsx b/frontend/src/component/project/Project/Import/configure/FileDropZone.tsx similarity index 100% rename from frontend/src/component/project/Project/Import/FileDropZone.tsx rename to frontend/src/component/project/Project/Import/configure/FileDropZone.tsx diff --git a/frontend/src/component/project/Project/Import/ImportExplanation.tsx b/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx similarity index 100% rename from frontend/src/component/project/Project/Import/ImportExplanation.tsx rename to frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx diff --git a/frontend/src/component/project/Project/Import/ImportOptions.tsx b/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx similarity index 86% rename from frontend/src/component/project/Project/Import/ImportOptions.tsx rename to frontend/src/component/project/Project/Import/configure/ImportOptions.tsx index 14c32fa764..48f1ef14de 100644 --- a/frontend/src/component/project/Project/Import/ImportOptions.tsx +++ b/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx @@ -1,7 +1,7 @@ -import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect'; +import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import { KeyboardArrowDownOutlined } from '@mui/icons-material'; -import React, { FC, useEffect, useState } from 'react'; -import { useProjectEnvironments } from '../../../../hooks/api/getters/useProjectEnvironments/useProjectEnvironments'; +import React, { FC, useEffect } from 'react'; +import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments'; import { Box, styled, Typography } from '@mui/material'; const ImportOptionsContainer = styled(Box)(({ theme }) => ({ @@ -12,7 +12,7 @@ const ImportOptionsContainer = styled(Box)(({ theme }) => ({ const ImportOptionsHeader = styled(Typography)(({ theme }) => ({ marginBottom: theme.spacing(3), - fontWeight: theme.typography.fontWeightBold, + fontWeight: theme.fontWeight.bold, })); const ImportOptionsDescription = styled(Typography)(({ theme }) => ({ diff --git a/frontend/src/component/project/Project/Import/PulsingAvatar.tsx b/frontend/src/component/project/Project/Import/configure/PulsingAvatar.tsx similarity index 100% rename from frontend/src/component/project/Project/Import/PulsingAvatar.tsx rename to frontend/src/component/project/Project/Import/configure/PulsingAvatar.tsx diff --git a/frontend/src/component/project/Project/Import/StyledFileDropZone.tsx b/frontend/src/component/project/Project/Import/configure/StyledFileDropZone.tsx similarity index 100% rename from frontend/src/component/project/Project/Import/StyledFileDropZone.tsx rename to frontend/src/component/project/Project/Import/configure/StyledFileDropZone.tsx diff --git a/frontend/src/component/project/Project/Import/validate/ValidationState.tsx b/frontend/src/component/project/Project/Import/validate/ValidationState.tsx new file mode 100644 index 0000000000..fc0d014d67 --- /dev/null +++ b/frontend/src/component/project/Project/Import/validate/ValidationState.tsx @@ -0,0 +1,44 @@ +import { ImportLayoutContainer } from '../ImportLayoutContainer'; +import { Box, styled, Typography } from '@mui/material'; +import { FC } from 'react'; + +const ImportInfoContainer = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.secondaryContainer, + borderRadius: theme.shape.borderRadiusLarge, + padding: theme.spacing(3), +})); + +const Label = styled('span')(({ theme }) => ({ + fontSize: theme.fontSizes.smallBody, + color: theme.palette.text.secondary, +})); +const Value = styled('span')(({ theme }) => ({ + fontSize: theme.fontSizes.smallBody, + color: theme.palette.text.primary, + fontWeight: theme.fontWeight.bold, +})); + +export const ValidationStage: FC<{ environment: string; project: string }> = ({ + environment, + project, +}) => { + return ( + + + + You are importing this configuration in: + + + + + {environment} + + + + {project} + + + + + ); +};