1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-03 01:18:43 +02:00

feat: back transition from validate to configure (#2982)

This commit is contained in:
Mateusz Kwasniewski 2023-01-25 10:11:08 +01:00 committed by GitHub
parent 85566b1431
commit e2e7f64b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 184 additions and 149 deletions

View File

@ -1,11 +1,18 @@
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ImportTimeline } from './ImportTimeline'; import { ImportTimeline } from './ImportTimeline';
import { ImportStage } from './ImportStage'; import { ImportStage } from './ImportStage';
import { ConfigurationStage } from './configure/ConfigurationStage'; import {
Actions,
ConfigurationStage,
ConfigurationTabs,
ImportArea,
ImportMode,
} from './configure/ConfigurationStage';
import { ValidationStage } from './validate/ValidationStage'; import { ValidationStage } from './validate/ValidationStage';
import { ImportOptions } from './configure/ImportOptions';
const ModalContentContainer = styled('div')(({ theme }) => ({ const ModalContentContainer = styled('div')(({ theme }) => ({
minHeight: '100vh', minHeight: '100vh',
@ -27,6 +34,15 @@ const TimelineHeader = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(4), marginBottom: theme.spacing(4),
})); }));
const isValidJSON = (json: string) => {
try {
JSON.parse(json);
return true;
} catch (e) {
return false;
}
};
interface IImportModalProps { interface IImportModalProps {
open: boolean; open: boolean;
setOpen: (value: boolean) => void; setOpen: (value: boolean) => void;
@ -34,9 +50,10 @@ interface IImportModalProps {
} }
export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => {
const [importStage, setImportStage] = useState<ImportStage>({ const [importStage, setImportStage] = useState<ImportStage>('configure');
name: 'configure', const [environment, setEnvironment] = useState('');
}); const [importPayload, setImportPayload] = useState('');
const [activeTab, setActiveTab] = useState<ImportMode>('file');
return ( return (
<SidebarModal <SidebarModal
@ -49,29 +66,49 @@ export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => {
<ModalContentContainer> <ModalContentContainer>
<TimelineContainer> <TimelineContainer>
<TimelineHeader>Process</TimelineHeader> <TimelineHeader>Process</TimelineHeader>
<ImportTimeline stage={importStage.name} /> <ImportTimeline stage={importStage} />
</TimelineContainer> </TimelineContainer>
<ConditionallyRender <ConditionallyRender
condition={importStage.name === 'configure'} condition={importStage === 'configure'}
show={ show={
<ConfigurationStage <ConfigurationStage
tabs={
<ConfigurationTabs
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
importOptions={
<ImportOptions
project={project} project={project}
environment={environment}
onChange={setEnvironment}
/>
}
importArea={
<ImportArea
activeTab={activeTab}
setActiveTab={setActiveTab}
importPayload={importPayload}
setImportPayload={setImportPayload}
/>
}
actions={
<Actions
disabled={!isValidJSON(importPayload)}
onSubmit={() => setImportStage('validate')}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onSubmit={configuration => />
setImportStage({
name: 'validate',
...configuration,
})
} }
/> />
} }
/> />
{importStage.name === 'validate' ? ( {importStage === 'validate' ? (
<ValidationStage <ValidationStage
project={project} project={project}
environment={importStage.environment} environment={environment}
payload={JSON.parse(importStage.payload)} payload={JSON.parse(importPayload)}
onBack={() => setImportStage({ name: 'configure' })} onBack={() => setImportStage('configure')}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
/> />
) : ( ) : (

View File

@ -1,4 +1 @@
export type ImportStage = export type ImportStage = 'configure' | 'validate' | 'import';
| { name: 'configure' }
| { name: 'validate'; environment: string; payload: string }
| { name: 'import' };

View File

@ -55,7 +55,7 @@ const TimelineItemDescription = styled(Box)(({ theme }) => ({
})); }));
export const ImportTimeline: FC<{ export const ImportTimeline: FC<{
stage: ImportStage['name']; stage: ImportStage;
}> = ({ stage }) => { }> = ({ stage }) => {
return ( return (
<StyledTimeline> <StyledTimeline>

View File

@ -7,13 +7,12 @@ import {
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import { ImportOptions } from './ImportOptions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StyledFileDropZone } from './StyledFileDropZone'; import { StyledFileDropZone } from './StyledFileDropZone';
import { PulsingAvatar } from './PulsingAvatar'; import { PulsingAvatar } from './PulsingAvatar';
import { ArrowUpward } from '@mui/icons-material'; import { ArrowUpward } from '@mui/icons-material';
import { ImportExplanation } from './ImportExplanation'; import { ImportExplanation } from './ImportExplanation';
import React, { FC, useState } from 'react'; import React, { FC, ReactNode, useState } from 'react';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { ImportLayoutContainer } from '../ImportLayoutContainer'; import { ImportLayoutContainer } from '../ImportLayoutContainer';
import { ActionsContainer } from '../ActionsContainer'; import { ActionsContainer } from '../ActionsContainer';
@ -38,35 +37,12 @@ const MaxSizeMessage = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
})); }));
type ImportMode = 'file' | 'code'; export type ImportMode = 'file' | 'code';
const isValidJSON = (json: string) => { export const ConfigurationTabs: FC<{
try { activeTab: ImportMode;
JSON.parse(json); setActiveTab: (mode: ImportMode) => void;
return true; }> = ({ activeTab, setActiveTab }) => (
} 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<ImportMode>('file');
const [dragActive, setDragActive] = useState(false);
const { setToastData } = useToast();
return (
<ImportLayoutContainer>
<Box <Box
sx={{ sx={{
borderBottom: 1, borderBottom: 1,
@ -86,11 +62,18 @@ export const ConfigurationStage: FC<{
/> />
</Tabs> </Tabs>
</Box> </Box>
<ImportOptions );
project={project}
environment={environment} export const ImportArea: FC<{
onChange={setEnvironment} activeTab: ImportMode;
/> setActiveTab: (mode: ImportMode) => void;
importPayload: string;
setImportPayload: (payload: string) => void;
}> = ({ activeTab, setActiveTab, importPayload, setImportPayload }) => {
const [dragActive, setDragActive] = useState(false);
const { setToastData } = useToast();
return (
<ConditionallyRender <ConditionallyRender
condition={activeTab === 'file'} condition={activeTab === 'file'}
show={ show={
@ -139,16 +122,21 @@ export const ConfigurationStage: FC<{
/> />
} }
/> />
<ImportExplanation /> );
};
export const Actions: FC<{
onSubmit: () => void;
onClose: () => void;
disabled: boolean;
}> = ({ onSubmit, onClose, disabled }) => (
<ActionsContainer> <ActionsContainer>
<Button <Button
sx={{ position: 'static' }} sx={{ position: 'static' }}
variant="contained" variant="contained"
type="submit" type="submit"
onClick={() => onClick={onSubmit}
onSubmit({ payload: importPayload, environment }) disabled={disabled}
}
disabled={!isValidJSON(importPayload)}
> >
Validate Validate
</Button> </Button>
@ -161,6 +149,21 @@ export const ConfigurationStage: FC<{
Cancel import Cancel import
</Button> </Button>
</ActionsContainer> </ActionsContainer>
);
export const ConfigurationStage: FC<{
tabs: ReactNode;
importOptions: ReactNode;
importArea: ReactNode;
actions: ReactNode;
}> = ({ tabs, importOptions, importArea, actions }) => {
return (
<ImportLayoutContainer>
{tabs}
{importOptions}
{importArea}
<ImportExplanation />
{actions}
</ImportLayoutContainer> </ImportLayoutContainer>
); );
}; };

View File

@ -39,10 +39,6 @@ export const ImportOptions: FC<IImportOptionsProps> = ({
title: environment.name, title: environment.name,
})); }));
useEffect(() => {
onChange(environmentOptions[0]?.key);
}, [JSON.stringify(environmentOptions)]);
return ( return (
<ImportOptionsContainer> <ImportOptionsContainer>
<ImportOptionsHeader>Import options</ImportOptionsHeader> <ImportOptionsHeader>Import options</ImportOptionsHeader>
@ -54,7 +50,7 @@ export const ImportOptions: FC<IImportOptionsProps> = ({
options={environmentOptions} options={environmentOptions}
onChange={onChange} onChange={onChange}
label={'Environment'} label={'Environment'}
value={environment} value={environment || environmentOptions[0]?.key}
IconComponent={KeyboardArrowDownOutlined} IconComponent={KeyboardArrowDownOutlined}
fullWidth fullWidth
/> />

View File

@ -92,16 +92,18 @@ export const ValidationStage: FC<{
const [validationResult, setValidationResult] = useState<IValidationSchema>( const [validationResult, setValidationResult] = useState<IValidationSchema>(
{ errors: [], warnings: [] } { errors: [], warnings: [] }
); );
const [validJSON, setValidJSON] = useState(true);
useEffect(() => { useEffect(() => {
validateImport({ environment, project, data: payload }) validateImport({ environment, project, data: payload })
.then(setValidationResult) .then(setValidationResult)
.catch(error => .catch(error => {
setValidJSON(false);
setToastData({ setToastData({
type: 'error', type: 'error',
title: formatUnknownError(error), title: formatUnknownError(error),
}) });
); });
}, []); }, []);
return ( return (
@ -185,7 +187,7 @@ export const ValidationStage: FC<{
sx={{ position: 'static' }} sx={{ position: 'static' }}
variant="contained" variant="contained"
type="submit" type="submit"
disabled={validationResult.errors.length > 0} disabled={validationResult.errors.length > 0 || !validJSON}
> >
Import configuration Import configuration
</Button> </Button>