mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
Makestyles refactor #7/1 (#2805)
This commit is contained in:
parent
cc1512cd44
commit
b631618532
@ -7,7 +7,7 @@ import {
|
|||||||
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
||||||
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
||||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
||||||
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
|
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs';
|
||||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||||
import { Divider, Typography } from '@mui/material';
|
import { Divider, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment';
|
|||||||
import { EditSegment } from 'component/segments/EditSegment/EditSegment';
|
import { EditSegment } from 'component/segments/EditSegment/EditSegment';
|
||||||
import { IRoute } from 'interfaces/route';
|
import { IRoute } from 'interfaces/route';
|
||||||
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
||||||
import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable';
|
import { SegmentTable } from 'component/segments/SegmentTable';
|
||||||
import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
|
import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
|
||||||
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
||||||
import { Billing } from 'component/admin/billing/Billing';
|
import { Billing } from 'component/admin/billing/Billing';
|
||||||
|
@ -10,9 +10,9 @@ import useToast from 'hooks/useToast';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
import { SegmentForm } from '../SegmentForm';
|
||||||
import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext';
|
import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext';
|
||||||
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
|
import { segmentsDocsLink } from 'component/segments/SegmentDocs';
|
||||||
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds';
|
import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds';
|
||||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||||
|
@ -11,10 +11,10 @@ import React from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
import { SegmentForm } from '../SegmentForm';
|
||||||
import { segmentsFormDescription } from 'component/segments/CreateSegment/CreateSegment';
|
import { segmentsFormDescription } from 'component/segments/CreateSegment/CreateSegment';
|
||||||
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
|
import { segmentsDocsLink } from 'component/segments/SegmentDocs';
|
||||||
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds';
|
import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds';
|
||||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||||
|
58
frontend/src/component/segments/SegmentEmpty.tsx
Normal file
58
frontend/src/component/segments/SegmentEmpty.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: theme.spacing(6),
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
marginBottom: theme.spacing(2.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledParagraph = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
maxWidth: 515,
|
||||||
|
marginBottom: theme.spacing(2.5),
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const SegmentEmpty = () => {
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledDiv>
|
||||||
|
<StyledTypography>No segments yet!</StyledTypography>
|
||||||
|
<StyledParagraph>
|
||||||
|
Segment makes it easy for you to define who should be exposed to
|
||||||
|
your feature. The segment is often a collection of constraints
|
||||||
|
and can be reused.
|
||||||
|
</StyledParagraph>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasAccess(CREATE_SEGMENT)}
|
||||||
|
show={
|
||||||
|
<StyledLink to="/segments/create">
|
||||||
|
Create your first segment
|
||||||
|
</StyledLink>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledDiv>
|
||||||
|
);
|
||||||
|
};
|
@ -1,29 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
empty: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: theme.spacing(6),
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
|
||||||
marginBottom: theme.spacing(2.5),
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
maxWidth: 515,
|
|
||||||
marginBottom: theme.spacing(2.5),
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
paramButton: {
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
fontWeight: theme.fontWeight.bold,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,31 +0,0 @@
|
|||||||
import { Typography } from '@mui/material';
|
|
||||||
import { useStyles } from 'component/segments/SegmentEmpty/SegmentEmpty.styles';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import AccessContext from 'contexts/AccessContext';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export const SegmentEmpty = () => {
|
|
||||||
const { classes } = useStyles();
|
|
||||||
const { hasAccess } = useContext(AccessContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.empty}>
|
|
||||||
<Typography className={classes.title}>No segments yet!</Typography>
|
|
||||||
<p className={classes.subtitle}>
|
|
||||||
Segment makes it easy for you to define who should be exposed to
|
|
||||||
your feature. The segment is often a collection of constraints
|
|
||||||
and can be reused.
|
|
||||||
</p>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasAccess(CREATE_SEGMENT)}
|
|
||||||
show={
|
|
||||||
<Link to="/segments/create" className={classes.paramButton}>
|
|
||||||
Create your first segment
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,14 +1,24 @@
|
|||||||
import { IConstraint } from 'interfaces/strategy';
|
import { IConstraint } from 'interfaces/strategy';
|
||||||
import { useStyles } from './SegmentForm.styles';
|
import { SegmentFormStepOne } from './SegmentFormStepOne';
|
||||||
import { SegmentFormStepOne } from '../SegmentFormStepOne/SegmentFormStepOne';
|
import { SegmentFormStepTwo } from './SegmentFormStepTwo';
|
||||||
import { SegmentFormStepTwo } from '../SegmentFormStepTwo/SegmentFormStepTwo';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { SegmentFormStepList } from 'component/segments/SegmentFormStepList/SegmentFormStepList';
|
import { SegmentFormStepList } from 'component/segments/SegmentFormStepList';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
export type SegmentFormStep = 1 | 2;
|
export type SegmentFormStep = 1 | 2;
|
||||||
export type SegmentFormMode = 'create' | 'edit';
|
export type SegmentFormMode = 'create' | 'edit';
|
||||||
|
|
||||||
|
const Styled = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: theme.spacing(6),
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}));
|
||||||
|
|
||||||
interface ISegmentProps {
|
interface ISegmentProps {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -22,6 +32,12 @@ interface ISegmentProps {
|
|||||||
mode: SegmentFormMode;
|
mode: SegmentFormMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
export const SegmentForm: React.FC<ISegmentProps> = ({
|
export const SegmentForm: React.FC<ISegmentProps> = ({
|
||||||
children,
|
children,
|
||||||
name,
|
name,
|
||||||
@ -35,14 +51,13 @@ export const SegmentForm: React.FC<ISegmentProps> = ({
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
mode,
|
mode,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const totalSteps = 2;
|
const totalSteps = 2;
|
||||||
const [currentStep, setCurrentStep] = useState<SegmentFormStep>(1);
|
const [currentStep, setCurrentStep] = useState<SegmentFormStep>(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SegmentFormStepList total={totalSteps} current={currentStep} />
|
<SegmentFormStepList total={totalSteps} current={currentStep} />
|
||||||
<form onSubmit={handleSubmit} className={styles.form}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={currentStep === 1}
|
condition={currentStep === 1}
|
||||||
show={
|
show={
|
||||||
@ -70,7 +85,7 @@ export const SegmentForm: React.FC<ISegmentProps> = ({
|
|||||||
</SegmentFormStepTwo>
|
</SegmentFormStepTwo>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</form>
|
</StyledForm>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,53 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
marginTop: '0',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
userInfoContainer: {
|
|
||||||
margin: '-20px 0',
|
|
||||||
},
|
|
||||||
errorAlert: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
flexRow: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
72
frontend/src/component/segments/SegmentFormStepList.tsx
Normal file
72
frontend/src/component/segments/SegmentFormStepList.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { FiberManualRecord } from '@mui/icons-material';
|
||||||
|
import React from 'react';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { formTemplateSidebarWidth } from '../common/FormTemplate/FormTemplate.styles';
|
||||||
|
|
||||||
|
interface ISegmentFormStepListProps {
|
||||||
|
total: number;
|
||||||
|
current: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
position: 'absolute',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
top: 30,
|
||||||
|
left: 0,
|
||||||
|
right: formTemplateSidebarWidth,
|
||||||
|
[theme.breakpoints.down(1100)]: {
|
||||||
|
right: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSteps = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
padding: theme.spacing(1, 2.5),
|
||||||
|
margin: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSpan = styled('span')(({ theme }) => ({
|
||||||
|
marginRight: 15,
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledFiberManualRecord = styled(FiberManualRecord, {
|
||||||
|
shouldForwardProp: prop => prop !== 'filled',
|
||||||
|
})<{ filled: boolean }>(({ theme, filled }) => ({
|
||||||
|
fill: theme.palette.primary.main,
|
||||||
|
transition: 'opacity 0.4s ease',
|
||||||
|
opacity: filled ? 1 : 0.4,
|
||||||
|
fontSize: filled ? 20 : 17,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const SegmentFormStepList: React.FC<ISegmentFormStepListProps> = ({
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
}) => {
|
||||||
|
// Create a list with all the step numbers, e.g. [1, 2, 3].
|
||||||
|
const steps: number[] = Array.from({ length: total }).map((_, i) => {
|
||||||
|
return i + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledSteps>
|
||||||
|
<StyledSpan>
|
||||||
|
Step {current} of {total}
|
||||||
|
</StyledSpan>
|
||||||
|
{steps.map(step => (
|
||||||
|
<StyledFiberManualRecord
|
||||||
|
key={step}
|
||||||
|
filled={step === current}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledSteps>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,40 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
import { formTemplateSidebarWidth } from 'component/common/FormTemplate/FormTemplate.styles';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
position: 'absolute',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
top: 30,
|
|
||||||
left: 0,
|
|
||||||
right: formTemplateSidebarWidth,
|
|
||||||
[theme.breakpoints.down(1100)]: {
|
|
||||||
right: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
steps: {
|
|
||||||
position: 'relative',
|
|
||||||
borderRadius: 10,
|
|
||||||
background: theme.palette.background.paper,
|
|
||||||
padding: '0.6rem 1.5rem',
|
|
||||||
margin: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
stepsText: {
|
|
||||||
marginRight: 15,
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
},
|
|
||||||
circle: {
|
|
||||||
fill: theme.palette.primary.main,
|
|
||||||
fontSize: 17,
|
|
||||||
opacity: 0.4,
|
|
||||||
transition: 'opacity 0.4s ease',
|
|
||||||
},
|
|
||||||
filledCircle: {
|
|
||||||
opacity: 1,
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,40 +0,0 @@
|
|||||||
import { FiberManualRecord } from '@mui/icons-material';
|
|
||||||
import { useStyles } from './SegmentFormStepList.styles';
|
|
||||||
import React from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
interface ISegmentFormStepListProps {
|
|
||||||
total: number;
|
|
||||||
current: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SegmentFormStepList: React.FC<ISegmentFormStepListProps> = ({
|
|
||||||
total,
|
|
||||||
current,
|
|
||||||
}) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
// Create a list with all the step numbers, e.g. [1, 2, 3].
|
|
||||||
const steps: number[] = Array.from({ length: total }).map((_, i) => {
|
|
||||||
return i + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.steps}>
|
|
||||||
<span className={styles.stepsText}>
|
|
||||||
Step {current} of {total}
|
|
||||||
</span>
|
|
||||||
{steps.map(step => (
|
|
||||||
<FiberManualRecord
|
|
||||||
key={step}
|
|
||||||
className={classNames(
|
|
||||||
styles.circle,
|
|
||||||
step === current && styles.filledCircle
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,9 +1,8 @@
|
|||||||
import { Button } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useStyles } from 'component/segments/SegmentFormStepOne/SegmentFormStepOne.styles';
|
import { SegmentFormStep } from './SegmentForm';
|
||||||
import { SegmentFormStep } from '../SegmentForm/SegmentForm';
|
|
||||||
import {
|
import {
|
||||||
SEGMENT_NAME_ID,
|
SEGMENT_NAME_ID,
|
||||||
SEGMENT_DESC_ID,
|
SEGMENT_DESC_ID,
|
||||||
@ -20,6 +19,35 @@ interface ISegmentFormPartOneProps {
|
|||||||
setCurrentStep: React.Dispatch<React.SetStateAction<SegmentFormStep>>;
|
setCurrentStep: React.Dispatch<React.SetStateAction<SegmentFormStep>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
maxWidth: '400px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||||
children,
|
children,
|
||||||
name,
|
name,
|
||||||
@ -31,16 +59,14 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
setCurrentStep,
|
setCurrentStep,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.form}>
|
<StyledForm>
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is the segment name?
|
What is the segment name?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
label="Segment name"
|
label="Segment name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
@ -50,11 +76,10 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
required
|
required
|
||||||
data-testid={SEGMENT_NAME_ID}
|
data-testid={SEGMENT_NAME_ID}
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is the segment description?
|
What is the segment description?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
label="Description (optional)"
|
label="Description (optional)"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={e => setDescription(e.target.value)}
|
onChange={e => setDescription(e.target.value)}
|
||||||
@ -62,8 +87,8 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
errorText={errors.description}
|
errorText={errors.description}
|
||||||
data-testid={SEGMENT_DESC_ID}
|
data-testid={SEGMENT_DESC_ID}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@ -74,16 +99,15 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
|||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<StyledCancelButton
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.cancelButton}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/segments');
|
navigate('/segments');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</div>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,49 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
userInfoContainer: {
|
|
||||||
margin: '-20px 0',
|
|
||||||
},
|
|
||||||
errorAlert: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
flexRow: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useState, useContext } from 'react';
|
import React, { useRef, useState, useContext } from 'react';
|
||||||
import { Button } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
@ -13,12 +13,11 @@ import {
|
|||||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||||
import { IConstraint } from 'interfaces/strategy';
|
import { IConstraint } from 'interfaces/strategy';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useStyles } from 'component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles';
|
|
||||||
import {
|
import {
|
||||||
ConstraintAccordionList,
|
ConstraintAccordionList,
|
||||||
IConstraintAccordionListRef,
|
IConstraintAccordionListRef,
|
||||||
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||||
import { SegmentFormStep, SegmentFormMode } from '../SegmentForm/SegmentForm';
|
import { SegmentFormStep, SegmentFormMode } from './SegmentForm';
|
||||||
import {
|
import {
|
||||||
AutocompleteBox,
|
AutocompleteBox,
|
||||||
IAutocompleteBoxOption,
|
IAutocompleteBoxOption,
|
||||||
@ -26,7 +25,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SegmentDocsValuesWarning,
|
SegmentDocsValuesWarning,
|
||||||
SegmentDocsValuesError,
|
SegmentDocsValuesError,
|
||||||
} from 'component/segments/SegmentDocs/SegmentDocs';
|
} from 'component/segments/SegmentDocs';
|
||||||
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||||
@ -38,6 +37,67 @@ interface ISegmentFormPartTwoProps {
|
|||||||
mode: SegmentFormMode;
|
mode: SegmentFormMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledWarning = styled('div')(({ theme }) => ({
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: '1rem',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAddContextContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: '1rem',
|
||||||
|
borderBottom: `1px solid ${theme.palette.grey[300]}`,
|
||||||
|
paddingBottom: '2rem',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledError = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: '1.5rem',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledNoConstraintText = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: theme.spacing(12),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSubtitle = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.bodySize,
|
||||||
|
color: theme.palette.tertiary.dark,
|
||||||
|
maxWidth: 515,
|
||||||
|
marginBottom: theme.spacing(2.5),
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledConstraintContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginBlock: theme.spacing(4),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
borderTop: `1px solid ${theme.palette.tertiary.contrast}`,
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledBackButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginRight: 'auto',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
||||||
children,
|
children,
|
||||||
constraints,
|
constraints,
|
||||||
@ -48,7 +108,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
const constraintsAccordionListRef = useRef<IConstraintAccordionListRef>();
|
const constraintsAccordionListRef = useRef<IConstraintAccordionListRef>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { context = [] } = useUnleashContext();
|
const { context = [] } = useUnleashContext();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const segmentValuesCount = useSegmentValuesCount(constraints);
|
const segmentValuesCount = useSegmentValuesCount(constraints);
|
||||||
@ -70,28 +129,28 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.form}>
|
<StyledForm>
|
||||||
<div className={styles.warning}>
|
<StyledWarning>
|
||||||
<SegmentDocsValuesWarning />
|
<SegmentDocsValuesWarning />
|
||||||
</div>
|
</StyledWarning>
|
||||||
<div>
|
<div>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
Select the context fields you want to include in the
|
Select the context fields you want to include in the
|
||||||
segment.
|
segment.
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
Use a predefined context field:
|
Use a predefined context field:
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<AutocompleteBox
|
<AutocompleteBox
|
||||||
label="Select a context"
|
label="Select a context"
|
||||||
options={autocompleteOptions}
|
options={autocompleteOptions}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.addContextContainer}>
|
<StyledAddContextContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
...or add a new context field:
|
...or add a new context field:
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
label="Create new context"
|
label="Create new context"
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
@ -113,26 +172,26 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
Add context field
|
Add context field
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
{overSegmentValuesLimit && (
|
{overSegmentValuesLimit && (
|
||||||
<div className={styles.error}>
|
<StyledError>
|
||||||
<SegmentDocsValuesError
|
<SegmentDocsValuesError
|
||||||
values={segmentValuesCount}
|
values={segmentValuesCount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</StyledAddContextContainer>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={constraints.length === 0}
|
condition={constraints.length === 0}
|
||||||
show={
|
show={
|
||||||
<div className={styles.noConstraintText}>
|
<StyledNoConstraintText>
|
||||||
<p className={styles.subtitle}>
|
<StyledSubtitle>
|
||||||
Start adding context fields by selecting an
|
Start adding context fields by selecting an
|
||||||
option from above, or you can create a new
|
option from above, or you can create a new
|
||||||
context field and use it right away
|
context field and use it right away
|
||||||
</p>
|
</StyledSubtitle>
|
||||||
</div>
|
</StyledNoConstraintText>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className={styles.constraintContainer}>
|
<StyledConstraintContainer>
|
||||||
<ConstraintAccordionList
|
<ConstraintAccordionList
|
||||||
ref={constraintsAccordionListRef}
|
ref={constraintsAccordionListRef}
|
||||||
constraints={constraints}
|
constraints={constraints}
|
||||||
@ -142,27 +201,25 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledConstraintContainer>
|
||||||
</div>
|
</StyledForm>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
<Button
|
<StyledBackButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCurrentStep(1)}
|
onClick={() => setCurrentStep(1)}
|
||||||
className={styles.backButton}
|
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</StyledBackButton>
|
||||||
{children}
|
{children}
|
||||||
<Button
|
<StyledCancelButton
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.cancelButton}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/segments');
|
navigate('/segments');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,102 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
warning: {
|
|
||||||
marginBottom: '1.5rem',
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
marginTop: '1.5rem',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
borderTop: `1px solid ${theme.palette.grey[300]}`,
|
|
||||||
paddingTop: 15,
|
|
||||||
},
|
|
||||||
errorsContainer: {
|
|
||||||
marginTop: '1rem',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
marginTop: '0',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
userInfoContainer: {
|
|
||||||
margin: '-20px 0',
|
|
||||||
},
|
|
||||||
errorAlert: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
flexRow: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
backButton: {
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
addContextContainer: {
|
|
||||||
marginTop: '1rem',
|
|
||||||
borderBottom: `1px solid ${theme.palette.grey[300]}`,
|
|
||||||
paddingBottom: '2rem',
|
|
||||||
},
|
|
||||||
addContextButton: {
|
|
||||||
color: theme.palette.primary.dark,
|
|
||||||
background: 'transparent',
|
|
||||||
boxShadow: 'none',
|
|
||||||
border: '1px solid',
|
|
||||||
'&:hover': {
|
|
||||||
background: 'transparent',
|
|
||||||
boxShadow: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderColor: `${theme.palette.grey[300]}`,
|
|
||||||
marginTop: '1rem !important',
|
|
||||||
},
|
|
||||||
noConstraintText: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: '6rem',
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
fontSize: theme.fontSizes.bodySize,
|
|
||||||
color: theme.palette.grey[600],
|
|
||||||
maxWidth: 515,
|
|
||||||
marginBottom: 20,
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
constraintContainer: {
|
|
||||||
marginBlock: '2rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -15,7 +15,7 @@ import { useMediaQuery } from '@mui/material';
|
|||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { SegmentEmpty } from 'component/segments/SegmentEmpty/SegmentEmpty';
|
import { SegmentEmpty } from 'component/segments/SegmentEmpty';
|
||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||||
import { DonutLarge } from '@mui/icons-material';
|
import { DonutLarge } from '@mui/icons-material';
|
||||||
import { SegmentActionCell } from 'component/segments/SegmentActionCell/SegmentActionCell';
|
import { SegmentActionCell } from 'component/segments/SegmentActionCell/SegmentActionCell';
|
@ -1,7 +1,7 @@
|
|||||||
import { useNavigate, Navigate } from 'react-router-dom';
|
import { useNavigate, Navigate } from 'react-router-dom';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
||||||
import { SplashPageOperators } from 'component/splash/SplashPageOperators/SplashPageOperators';
|
import { SplashPageOperators } from 'component/splash/SplashPageOperators';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
||||||
import { splashIds, SplashId } from 'component/splash/splash';
|
import { splashIds, SplashId } from 'component/splash/splash';
|
||||||
|
170
frontend/src/component/splash/SplashPageOperators.tsx
Normal file
170
frontend/src/component/splash/SplashPageOperators.tsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { Button, IconButton, styled } from '@mui/material';
|
||||||
|
import { CloseOutlined } from '@mui/icons-material';
|
||||||
|
import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert';
|
||||||
|
|
||||||
|
const StyledContainer = styled('section')(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
minHeight: '100vh',
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
display: 'grid',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
alignItems: 'center',
|
||||||
|
alignContent: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gridTemplateColumns: 'minmax(0,auto)',
|
||||||
|
fontWeight: theme.fontWeight.thin,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContent = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
padding: theme.spacing(4),
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
padding: theme.spacing(8),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('header')(({ theme }) => ({
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('h1')(({ theme }) => ({
|
||||||
|
fontWeight: 'inherit',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCloseButton = styled(IconButton)(({ theme }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
color: 'inherit',
|
||||||
|
}));
|
||||||
|
const StyledIngress = styled('p')(({ theme }) => ({
|
||||||
|
maxWidth: theme.spacing(64),
|
||||||
|
margin: theme.spacing(3, 'auto', 0, 'auto'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledBody = styled('div')(({ theme }) => ({
|
||||||
|
margin: theme.spacing(4, 0),
|
||||||
|
padding: theme.spacing(4, 0),
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderTopColor: theme.palette.primary.light,
|
||||||
|
borderBottomColor: theme.palette.primary.light,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledList = styled('ul')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2, 0),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
padding: theme.spacing(2, 4),
|
||||||
|
},
|
||||||
|
'& li + li': {
|
||||||
|
marginTop: theme.spacing(0.5),
|
||||||
|
},
|
||||||
|
'& strong': {
|
||||||
|
padding: theme.spacing(0, 0.5),
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
fontWeight: 'inherit',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledFooter = styled('footer')(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gap: theme.spacing(4),
|
||||||
|
textAlign: 'center',
|
||||||
|
justifyItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLink = styled('a')(({ theme }) => ({
|
||||||
|
color: 'inherit',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const SplashPageOperators = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledContent>
|
||||||
|
<StyledHeader>
|
||||||
|
<StyledTitle>New strategy operators</StyledTitle>
|
||||||
|
<StyledCloseButton
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<CloseOutlined titleAccess="Close" />
|
||||||
|
</StyledCloseButton>
|
||||||
|
<StyledIngress>
|
||||||
|
We've added some new feature strategy constraint
|
||||||
|
operators. Fine-tune your feature targeting like never
|
||||||
|
before.
|
||||||
|
</StyledIngress>
|
||||||
|
</StyledHeader>
|
||||||
|
<StyledBody>
|
||||||
|
<p>For example:</p>
|
||||||
|
<StyledList>
|
||||||
|
<li>
|
||||||
|
<span>Toggle features at dates: </span>
|
||||||
|
<span>
|
||||||
|
<strong>DATE_BEFORE</strong>{' '}
|
||||||
|
<strong>DATE_AFTER</strong>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Toggle features for versions: </span>
|
||||||
|
<span>
|
||||||
|
<strong>SEMVER_EQ</strong>{' '}
|
||||||
|
<strong>SEMVER_GT</strong>{' '}
|
||||||
|
<strong>SEMVER_LT</strong>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Toggle features for strings: </span>
|
||||||
|
<span>
|
||||||
|
<strong>STR_CONTAINS</strong>{' '}
|
||||||
|
<strong>STR_ENDS_WITH</strong>{' '}
|
||||||
|
<strong>STR_STARTS_WITH</strong>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Toggle features for numbers: </span>
|
||||||
|
<span>
|
||||||
|
<strong>NUM_GT</strong> <strong>NUM_GTE</strong>{' '}
|
||||||
|
<strong>NUM_LT</strong> <strong>NUM_LTE</strong>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</StyledList>
|
||||||
|
</StyledBody>
|
||||||
|
<StyledFooter>
|
||||||
|
<p>
|
||||||
|
<StyledLink
|
||||||
|
href="https://docs.getunleash.io/reference/strategy-constraints#numeric-operators"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Read all about operators in our in-depth{' '}
|
||||||
|
<strong>docs</strong>
|
||||||
|
</StyledLink>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Button
|
||||||
|
sx={theme => ({
|
||||||
|
background: 'white !important',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
})}
|
||||||
|
variant="contained"
|
||||||
|
component={Link}
|
||||||
|
to="/"
|
||||||
|
>
|
||||||
|
Fine, whatever, I have work to do!
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
</StyledFooter>
|
||||||
|
</StyledContent>
|
||||||
|
<OperatorUpgradeAlert />
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,78 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
minHeight: '100vh',
|
|
||||||
padding: '1rem',
|
|
||||||
display: 'grid',
|
|
||||||
gap: '1rem',
|
|
||||||
alignItems: 'center',
|
|
||||||
alignContent: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gridTemplateColumns: 'minmax(0,auto)',
|
|
||||||
fontWeight: theme.fontWeight.thin,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
position: 'relative',
|
|
||||||
padding: '2rem',
|
|
||||||
borderRadius: '0.5rem',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: 'white',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
padding: '4rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
display: 'grid',
|
|
||||||
gap: '2rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyItems: 'center',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
margin: '2rem 0',
|
|
||||||
padding: '2rem 0',
|
|
||||||
borderTop: '1px solid',
|
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderTopColor: theme.palette.primary.light,
|
|
||||||
borderBottomColor: theme.palette.primary.light,
|
|
||||||
},
|
|
||||||
close: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
color: 'inherit',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontWeight: 'inherit',
|
|
||||||
},
|
|
||||||
ingress: {
|
|
||||||
maxWidth: '32rem',
|
|
||||||
margin: '1.5rem auto 0 auto',
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
padding: '1rem 0',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
padding: '1rem 2rem',
|
|
||||||
},
|
|
||||||
'& li + li': {
|
|
||||||
marginTop: '0.25rem',
|
|
||||||
},
|
|
||||||
'& strong': {
|
|
||||||
padding: '0 .2rem',
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
fontWeight: 'inherit',
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: 'inherit',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
background: 'white !important',
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,92 +0,0 @@
|
|||||||
import { useStyles } from 'component/splash/SplashPageOperators/SplashPageOperators.styles';
|
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
|
||||||
import { Button, IconButton } from '@mui/material';
|
|
||||||
import { CloseOutlined } from '@mui/icons-material';
|
|
||||||
import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert';
|
|
||||||
|
|
||||||
export const SplashPageOperators = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.container}>
|
|
||||||
<div className={styles.content}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<h1 className={styles.title}>New strategy operators</h1>
|
|
||||||
<IconButton
|
|
||||||
className={styles.close}
|
|
||||||
onClick={() => navigate('/')}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<CloseOutlined titleAccess="Close" />
|
|
||||||
</IconButton>
|
|
||||||
<p className={styles.ingress}>
|
|
||||||
We've added some new feature strategy constraint
|
|
||||||
operators. Fine-tune your feature targeting like never
|
|
||||||
before.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
<div className={styles.body}>
|
|
||||||
<p>For example:</p>
|
|
||||||
<ul className={styles.list}>
|
|
||||||
<li>
|
|
||||||
<span>Toggle features at dates: </span>
|
|
||||||
<span>
|
|
||||||
<strong>DATE_BEFORE</strong>{' '}
|
|
||||||
<strong>DATE_AFTER</strong>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Toggle features for versions: </span>
|
|
||||||
<span>
|
|
||||||
<strong>SEMVER_EQ</strong>{' '}
|
|
||||||
<strong>SEMVER_GT</strong>{' '}
|
|
||||||
<strong>SEMVER_LT</strong>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Toggle features for strings: </span>
|
|
||||||
<span>
|
|
||||||
<strong>STR_CONTAINS</strong>{' '}
|
|
||||||
<strong>STR_ENDS_WITH</strong>{' '}
|
|
||||||
<strong>STR_STARTS_WITH</strong>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Toggle features for numbers: </span>
|
|
||||||
<span>
|
|
||||||
<strong>NUM_GT</strong> <strong>NUM_GTE</strong>{' '}
|
|
||||||
<strong>NUM_LT</strong> <strong>NUM_LTE</strong>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer className={styles.footer}>
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
href="https://docs.getunleash.io/reference/strategy-constraints#numeric-operators"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className={styles.link}
|
|
||||||
>
|
|
||||||
Read all about operators in our in-depth{' '}
|
|
||||||
<strong>docs</strong>
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Button
|
|
||||||
className={styles.button}
|
|
||||||
variant="contained"
|
|
||||||
component={Link}
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
Fine, whatever, I have work to do!
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<OperatorUpgradeAlert />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,61 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: 400,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
selectInput: {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
minWidth: '400px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: '379px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: theme.palette.primary.light,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
typeDescription: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.grey[600],
|
|
||||||
top: '-13px',
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
flexRow: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: '0.5rem',
|
|
||||||
},
|
|
||||||
paramButton: {
|
|
||||||
color: theme.palette.primary.dark,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,6 +1,5 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { Button } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import { useStyles } from './StrategyForm.styles';
|
|
||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
import { StrategyParameters } from './StrategyParameters/StrategyParameters';
|
import { StrategyParameters } from './StrategyParameters/StrategyParameters';
|
||||||
@ -23,6 +22,39 @@ interface IStrategyFormProps {
|
|||||||
setErrors: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
setErrors: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
maxWidth: 400,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledParamButton = styled(Button)(({ theme }) => ({
|
||||||
|
color: theme.palette.primary.dark,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -38,7 +70,6 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
|||||||
mode,
|
mode,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const updateParameter = (index: number, updated: object) => {
|
const updateParameter = (index: number, updated: object) => {
|
||||||
let item = { ...params[index] };
|
let item = { ...params[index] };
|
||||||
params[index] = Object.assign({}, item, updated);
|
params[index] = Object.assign({}, item, updated);
|
||||||
@ -53,15 +84,14 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={styles.form}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What would you like to call your strategy?
|
What would you like to call your strategy?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
disabled={mode === 'Edit'}
|
disabled={mode === 'Edit'}
|
||||||
autoFocus
|
autoFocus
|
||||||
className={styles.input}
|
|
||||||
label="Strategy name*"
|
label="Strategy name*"
|
||||||
value={strategyName}
|
value={strategyName}
|
||||||
onChange={e => setStrategyName(trim(e.target.value))}
|
onChange={e => setStrategyName(trim(e.target.value))}
|
||||||
@ -70,11 +100,10 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
|||||||
onFocus={clearErrors}
|
onFocus={clearErrors}
|
||||||
onBlur={validateStrategyName}
|
onBlur={validateStrategyName}
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is your strategy description?
|
What is your strategy description?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
label="Strategy description"
|
label="Strategy description"
|
||||||
value={strategyDesc}
|
value={strategyDesc}
|
||||||
onChange={e => setStrategyDesc(e.target.value)}
|
onChange={e => setStrategyDesc(e.target.value)}
|
||||||
@ -88,29 +117,24 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
|||||||
setParams={setParams}
|
setParams={setParams}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<Button
|
<StyledParamButton
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
appParameter();
|
appParameter();
|
||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className={styles.paramButton}
|
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
>
|
>
|
||||||
Add parameter
|
Add parameter
|
||||||
</Button>
|
</StyledParamButton>
|
||||||
</div>
|
</StyledContainer>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<Button
|
<StyledCancelButton type="button" onClick={handleCancel}>
|
||||||
type="button"
|
|
||||||
onClick={handleCancel}
|
|
||||||
className={styles.cancelButton}
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
paramsContainer: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
margin: '1rem 0',
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
borderStyle: 'dashed',
|
|
||||||
margin: '1rem 0 1.5rem 0',
|
|
||||||
borderColor: theme.palette.grey[500],
|
|
||||||
},
|
|
||||||
nameContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
minWidth: '365px',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
minWidth: '365px',
|
|
||||||
width: '100%',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
minWidth: '365px',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
checkboxLabel: {
|
|
||||||
marginTop: '-0.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
paramButton: {
|
|
||||||
color: theme.palette.primary.dark,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -3,10 +3,10 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
styled,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Delete } from '@mui/icons-material';
|
import { Delete } from '@mui/icons-material';
|
||||||
import { useStyles } from './StrategyParameter.styles';
|
|
||||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
@ -45,6 +45,49 @@ interface IStrategyParameterProps {
|
|||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledParamsContainer = styled('div')(({ theme }) => ({
|
||||||
|
maxWidth: '400px',
|
||||||
|
margin: theme.spacing(2, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
margin: theme.spacing(2, 0, 3, 0),
|
||||||
|
borderColor: theme.palette.neutral.border,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledParagraph = styled('p')(({ theme }) => ({
|
||||||
|
minWidth: '365px',
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledNameContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledNameInput = styled(Input)(({ theme }) => ({
|
||||||
|
minWidth: '365px',
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledGeneralSelect = styled(GeneralSelect)(({ theme }) => ({
|
||||||
|
minWidth: '365px',
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDescriptionInput = styled(Input)(({ theme }) => ({
|
||||||
|
minWidth: '365px',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(-1),
|
||||||
|
}));
|
||||||
|
|
||||||
export const StrategyParameter = ({
|
export const StrategyParameter = ({
|
||||||
set,
|
set,
|
||||||
input,
|
input,
|
||||||
@ -53,19 +96,17 @@ export const StrategyParameter = ({
|
|||||||
setParams,
|
setParams,
|
||||||
errors,
|
errors,
|
||||||
}: IStrategyParameterProps) => {
|
}: IStrategyParameterProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
const onTypeChange = (type: string) => {
|
const onTypeChange = (type: string) => {
|
||||||
set({ type });
|
set({ type });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.paramsContainer}>
|
<StyledParamsContainer>
|
||||||
<Divider className={styles.divider} />
|
<StyledDivider />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={index === 0}
|
condition={index === 0}
|
||||||
show={
|
show={
|
||||||
<p className={styles.input}>
|
<StyledParagraph>
|
||||||
Parameters let you provide arguments to your strategy
|
Parameters let you provide arguments to your strategy
|
||||||
that it can access for evaluation. Read more in the{' '}
|
that it can access for evaluation. Read more in the{' '}
|
||||||
<a
|
<a
|
||||||
@ -76,16 +117,15 @@ export const StrategyParameter = ({
|
|||||||
parameter types documentation
|
parameter types documentation
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</p>
|
</StyledParagraph>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className={styles.nameContainer}>
|
<StyledNameContainer>
|
||||||
<Input
|
<StyledNameInput
|
||||||
autoFocus
|
autoFocus
|
||||||
label={`Parameter name ${index + 1}*`}
|
label={`Parameter name ${index + 1}*`}
|
||||||
onChange={e => set({ name: e.target.value })}
|
onChange={e => set({ name: e.target.value })}
|
||||||
value={input.name}
|
value={input.name}
|
||||||
className={styles.name}
|
|
||||||
error={Boolean(errors?.[`paramName${index}`])}
|
error={Boolean(errors?.[`paramName${index}`])}
|
||||||
errorText={errors?.[`paramName${index}`]}
|
errorText={errors?.[`paramName${index}`]}
|
||||||
/>
|
/>
|
||||||
@ -99,25 +139,23 @@ export const StrategyParameter = ({
|
|||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</StyledNameContainer>
|
||||||
<GeneralSelect
|
<StyledGeneralSelect
|
||||||
label="Type*"
|
label="Type*"
|
||||||
name="type"
|
name="type"
|
||||||
options={paramTypesOptions}
|
options={paramTypesOptions}
|
||||||
value={input.type}
|
value={input.type}
|
||||||
onChange={onTypeChange}
|
onChange={onTypeChange}
|
||||||
id={`prop-type-${index}-select`}
|
id={`prop-type-${index}-select`}
|
||||||
className={styles.input}
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<StyledDescriptionInput
|
||||||
rows={2}
|
rows={2}
|
||||||
multiline
|
multiline
|
||||||
label={`Parameter name ${index + 1} description`}
|
label={`Parameter name ${index + 1} description`}
|
||||||
onChange={({ target }) => set({ description: target.value })}
|
onChange={({ target }) => set({ description: target.value })}
|
||||||
value={input.description}
|
value={input.description}
|
||||||
className={styles.description}
|
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<StyledFormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={Boolean(input.required)}
|
checked={Boolean(input.required)}
|
||||||
@ -125,8 +163,7 @@ export const StrategyParameter = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Required"
|
label="Required"
|
||||||
className={styles.checkboxLabel}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledParamsContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
permissionErrorContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,7 +1,6 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { TextField, Button } from '@mui/material';
|
import { TextField, Button, styled } from '@mui/material';
|
||||||
|
|
||||||
import { useStyles } from './TagTypeForm.styles';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
import { EDIT } from 'constants/misc';
|
import { EDIT } from 'constants/misc';
|
||||||
@ -19,6 +18,40 @@ interface ITagTypeForm {
|
|||||||
validateNameUniqueness?: () => void;
|
validateNameUniqueness?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
maxWidth: '400px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCancelButton = styled(Button)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
const TagTypeForm: React.FC<ITagTypeForm> = ({
|
const TagTypeForm: React.FC<ITagTypeForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -32,16 +65,13 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
|||||||
validateNameUniqueness,
|
validateNameUniqueness,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={styles.form}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div className={styles.container}>
|
<StyledContainer>
|
||||||
<p className={styles.inputDescription}>
|
<StyledInputDescription>
|
||||||
What is your tag name?
|
What is your tag name?
|
||||||
</p>
|
</StyledInputDescription>
|
||||||
<Input
|
<StyledInput
|
||||||
className={styles.input}
|
|
||||||
label="Tag name"
|
label="Tag name"
|
||||||
value={tagName}
|
value={tagName}
|
||||||
onChange={e => setTagName(trim(e.target.value))}
|
onChange={e => setTagName(trim(e.target.value))}
|
||||||
@ -53,9 +83,10 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className={styles.inputDescription}>What is this tag for?</p>
|
<StyledInputDescription>
|
||||||
<TextField
|
What is this tag for?
|
||||||
className={styles.input}
|
</StyledInputDescription>
|
||||||
|
<StyledTextField
|
||||||
label="Tag description"
|
label="Tag description"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
multiline
|
multiline
|
||||||
@ -63,14 +94,14 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
|||||||
value={tagDesc}
|
value={tagDesc}
|
||||||
onChange={e => setTagDesc(e.target.value)}
|
onChange={e => setTagDesc(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
<div className={styles.buttonContainer}>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
<StyledCancelButton onClick={handleCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</StyledCancelButton>
|
||||||
</div>
|
</StyledButtonContainer>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
width: '150px',
|
|
||||||
margin: '1rem auto',
|
|
||||||
display: 'block',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,20 +1,31 @@
|
|||||||
import { Button } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import classnames from 'classnames';
|
|
||||||
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import PasswordChecker from './PasswordChecker/PasswordChecker';
|
import PasswordChecker from './PasswordChecker/PasswordChecker';
|
||||||
import PasswordMatcher from './PasswordMatcher/PasswordMatcher';
|
import PasswordMatcher from './PasswordMatcher/PasswordMatcher';
|
||||||
import { useStyles } from './ResetPasswordForm.styles';
|
|
||||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||||
|
|
||||||
interface IResetPasswordProps {
|
interface IResetPasswordProps {
|
||||||
onSubmit: (password: string) => void;
|
onSubmit: (password: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
|
'& > *': {
|
||||||
|
marginTop: `${theme.spacing(1)} !important`,
|
||||||
|
marginBottom: `${theme.spacing(1)} !important`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
width: '150px',
|
||||||
|
margin: theme.spacing(2, 'auto'),
|
||||||
|
display: 'block',
|
||||||
|
}));
|
||||||
|
|
||||||
const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPasswordChecker, setShowPasswordChecker] = useState(false);
|
const [showPasswordChecker, setShowPasswordChecker] = useState(false);
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
@ -50,13 +61,7 @@ const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
|||||||
const started = Boolean(password && confirmPassword);
|
const started = Boolean(password && confirmPassword);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
onSubmit={handleSubmit}
|
|
||||||
className={classnames(
|
|
||||||
themeStyles.contentSpacingY,
|
|
||||||
styles.container
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<PasswordField
|
<PasswordField
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
value={password || ''}
|
value={password || ''}
|
||||||
@ -91,17 +96,16 @@ const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
|||||||
started={started}
|
started={started}
|
||||||
matchingPasswords={matchingPasswords}
|
matchingPasswords={matchingPasswords}
|
||||||
/>
|
/>
|
||||||
<Button
|
<StyledButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
className={styles.button}
|
|
||||||
data-loading
|
data-loading
|
||||||
disabled={!submittable}
|
disabled={!submittable}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</StyledButton>
|
||||||
</form>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user