mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +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';
|
||||
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
||||
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 { Divider, Typography } from '@mui/material';
|
||||
|
||||
|
@ -48,7 +48,7 @@ import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment';
|
||||
import { EditSegment } from 'component/segments/EditSegment/EditSegment';
|
||||
import { IRoute } from 'interfaces/route';
|
||||
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 { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
||||
import { Billing } from 'component/admin/billing/Billing';
|
||||
|
@ -10,9 +10,9 @@ import useToast from 'hooks/useToast';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
||||
import { SegmentForm } from '../SegmentForm';
|
||||
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 { SEGMENT_CREATE_BTN_ID } from 'utils/testIds';
|
||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||
|
@ -11,10 +11,10 @@ import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
||||
import { SegmentForm } from '../SegmentForm';
|
||||
import { segmentsFormDescription } from 'component/segments/CreateSegment/CreateSegment';
|
||||
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 { SEGMENT_SAVE_BTN_ID } from 'utils/testIds';
|
||||
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 { useStyles } from './SegmentForm.styles';
|
||||
import { SegmentFormStepOne } from '../SegmentFormStepOne/SegmentFormStepOne';
|
||||
import { SegmentFormStepTwo } from '../SegmentFormStepTwo/SegmentFormStepTwo';
|
||||
import { SegmentFormStepOne } from './SegmentFormStepOne';
|
||||
import { SegmentFormStepTwo } from './SegmentFormStepTwo';
|
||||
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 { styled } from '@mui/material';
|
||||
|
||||
export type SegmentFormStep = 1 | 2;
|
||||
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 {
|
||||
name: string;
|
||||
description: string;
|
||||
@ -22,6 +32,12 @@ interface ISegmentProps {
|
||||
mode: SegmentFormMode;
|
||||
}
|
||||
|
||||
const StyledForm = styled('form')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
}));
|
||||
|
||||
export const SegmentForm: React.FC<ISegmentProps> = ({
|
||||
children,
|
||||
name,
|
||||
@ -35,14 +51,13 @@ export const SegmentForm: React.FC<ISegmentProps> = ({
|
||||
clearErrors,
|
||||
mode,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const totalSteps = 2;
|
||||
const [currentStep, setCurrentStep] = useState<SegmentFormStep>(1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SegmentFormStepList total={totalSteps} current={currentStep} />
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<ConditionallyRender
|
||||
condition={currentStep === 1}
|
||||
show={
|
||||
@ -70,7 +85,7 @@ export const SegmentForm: React.FC<ISegmentProps> = ({
|
||||
</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 React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useStyles } from 'component/segments/SegmentFormStepOne/SegmentFormStepOne.styles';
|
||||
import { SegmentFormStep } from '../SegmentForm/SegmentForm';
|
||||
import { SegmentFormStep } from './SegmentForm';
|
||||
import {
|
||||
SEGMENT_NAME_ID,
|
||||
SEGMENT_DESC_ID,
|
||||
@ -20,6 +19,35 @@ interface ISegmentFormPartOneProps {
|
||||
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> = ({
|
||||
children,
|
||||
name,
|
||||
@ -31,16 +59,14 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||
setCurrentStep,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.form}>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledForm>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What is the segment name?
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label="Segment name"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
@ -50,11 +76,10 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||
required
|
||||
data-testid={SEGMENT_NAME_ID}
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
What is the segment description?
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label="Description (optional)"
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
@ -62,8 +87,8 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||
errorText={errors.description}
|
||||
data-testid={SEGMENT_DESC_ID}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
</StyledContainer>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
@ -74,16 +99,15 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
<Button
|
||||
<StyledCancelButton
|
||||
type="button"
|
||||
className={styles.cancelButton}
|
||||
onClick={() => {
|
||||
navigate('/segments');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</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 { Button } from '@mui/material';
|
||||
import { Button, styled } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
@ -13,12 +13,11 @@ import {
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useStyles } from 'component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles';
|
||||
import {
|
||||
ConstraintAccordionList,
|
||||
IConstraintAccordionListRef,
|
||||
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||
import { SegmentFormStep, SegmentFormMode } from '../SegmentForm/SegmentForm';
|
||||
import { SegmentFormStep, SegmentFormMode } from './SegmentForm';
|
||||
import {
|
||||
AutocompleteBox,
|
||||
IAutocompleteBoxOption,
|
||||
@ -26,7 +25,7 @@ import {
|
||||
import {
|
||||
SegmentDocsValuesWarning,
|
||||
SegmentDocsValuesError,
|
||||
} from 'component/segments/SegmentDocs/SegmentDocs';
|
||||
} from 'component/segments/SegmentDocs';
|
||||
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
|
||||
@ -38,6 +37,67 @@ interface ISegmentFormPartTwoProps {
|
||||
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> = ({
|
||||
children,
|
||||
constraints,
|
||||
@ -48,7 +108,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
||||
const constraintsAccordionListRef = useRef<IConstraintAccordionListRef>();
|
||||
const navigate = useNavigate();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const { classes: styles } = useStyles();
|
||||
const { context = [] } = useUnleashContext();
|
||||
const [open, setOpen] = useState(false);
|
||||
const segmentValuesCount = useSegmentValuesCount(constraints);
|
||||
@ -70,28 +129,28 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.warning}>
|
||||
<StyledForm>
|
||||
<StyledWarning>
|
||||
<SegmentDocsValuesWarning />
|
||||
</div>
|
||||
</StyledWarning>
|
||||
<div>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
Select the context fields you want to include in the
|
||||
segment.
|
||||
</p>
|
||||
<p className={styles.inputDescription}>
|
||||
</StyledInputDescription>
|
||||
<StyledInputDescription>
|
||||
Use a predefined context field:
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<AutocompleteBox
|
||||
label="Select a context"
|
||||
options={autocompleteOptions}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.addContextContainer}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledAddContextContainer>
|
||||
<StyledInputDescription>
|
||||
...or add a new context field:
|
||||
</p>
|
||||
</StyledInputDescription>
|
||||
<SidebarModal
|
||||
label="Create new context"
|
||||
onClose={() => setOpen(false)}
|
||||
@ -113,26 +172,26 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
||||
Add context field
|
||||
</PermissionButton>
|
||||
{overSegmentValuesLimit && (
|
||||
<div className={styles.error}>
|
||||
<StyledError>
|
||||
<SegmentDocsValuesError
|
||||
values={segmentValuesCount}
|
||||
/>
|
||||
</div>
|
||||
</StyledError>
|
||||
)}
|
||||
</div>
|
||||
</StyledAddContextContainer>
|
||||
<ConditionallyRender
|
||||
condition={constraints.length === 0}
|
||||
show={
|
||||
<div className={styles.noConstraintText}>
|
||||
<p className={styles.subtitle}>
|
||||
<StyledNoConstraintText>
|
||||
<StyledSubtitle>
|
||||
Start adding context fields by selecting an
|
||||
option from above, or you can create a new
|
||||
context field and use it right away
|
||||
</p>
|
||||
</div>
|
||||
</StyledSubtitle>
|
||||
</StyledNoConstraintText>
|
||||
}
|
||||
/>
|
||||
<div className={styles.constraintContainer}>
|
||||
<StyledConstraintContainer>
|
||||
<ConstraintAccordionList
|
||||
ref={constraintsAccordionListRef}
|
||||
constraints={constraints}
|
||||
@ -142,27 +201,25 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button
|
||||
</StyledConstraintContainer>
|
||||
</StyledForm>
|
||||
<StyledButtonContainer>
|
||||
<StyledBackButton
|
||||
type="button"
|
||||
onClick={() => setCurrentStep(1)}
|
||||
className={styles.backButton}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</StyledBackButton>
|
||||
{children}
|
||||
<Button
|
||||
<StyledCancelButton
|
||||
type="button"
|
||||
className={styles.cancelButton}
|
||||
onClick={() => {
|
||||
navigate('/segments');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</StyledCancelButton>
|
||||
</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 { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
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 { DonutLarge } from '@mui/icons-material';
|
||||
import { SegmentActionCell } from 'component/segments/SegmentActionCell/SegmentActionCell';
|
@ -1,7 +1,7 @@
|
||||
import { useNavigate, Navigate } from 'react-router-dom';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
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 { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
||||
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 { Button } from '@mui/material';
|
||||
import { useStyles } from './StrategyForm.styles';
|
||||
import { Button, styled } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { trim } from 'component/common/util';
|
||||
import { StrategyParameters } from './StrategyParameters/StrategyParameters';
|
||||
@ -23,6 +22,39 @@ interface IStrategyFormProps {
|
||||
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> = ({
|
||||
children,
|
||||
handleSubmit,
|
||||
@ -38,7 +70,6 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
mode,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const updateParameter = (index: number, updated: object) => {
|
||||
let item = { ...params[index] };
|
||||
params[index] = Object.assign({}, item, updated);
|
||||
@ -53,15 +84,14 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What would you like to call your strategy?
|
||||
</p>
|
||||
<Input
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
disabled={mode === 'Edit'}
|
||||
autoFocus
|
||||
className={styles.input}
|
||||
label="Strategy name*"
|
||||
value={strategyName}
|
||||
onChange={e => setStrategyName(trim(e.target.value))}
|
||||
@ -70,11 +100,10 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
onFocus={clearErrors}
|
||||
onBlur={validateStrategyName}
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledInputDescription>
|
||||
What is your strategy description?
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label="Strategy description"
|
||||
value={strategyDesc}
|
||||
onChange={e => setStrategyDesc(e.target.value)}
|
||||
@ -88,29 +117,24 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
setParams={setParams}
|
||||
errors={errors}
|
||||
/>
|
||||
<Button
|
||||
<StyledParamButton
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
appParameter();
|
||||
}}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className={styles.paramButton}
|
||||
startIcon={<Add />}
|
||||
>
|
||||
Add parameter
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
</StyledParamButton>
|
||||
</StyledContainer>
|
||||
<StyledButtonContainer>
|
||||
{children}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
className={styles.cancelButton}
|
||||
>
|
||||
<StyledCancelButton type="button" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</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,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
styled,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { Delete } from '@mui/icons-material';
|
||||
import { useStyles } from './StrategyParameter.styles';
|
||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
@ -45,6 +45,49 @@ interface IStrategyParameterProps {
|
||||
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 = ({
|
||||
set,
|
||||
input,
|
||||
@ -53,19 +96,17 @@ export const StrategyParameter = ({
|
||||
setParams,
|
||||
errors,
|
||||
}: IStrategyParameterProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const onTypeChange = (type: string) => {
|
||||
set({ type });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.paramsContainer}>
|
||||
<Divider className={styles.divider} />
|
||||
<StyledParamsContainer>
|
||||
<StyledDivider />
|
||||
<ConditionallyRender
|
||||
condition={index === 0}
|
||||
show={
|
||||
<p className={styles.input}>
|
||||
<StyledParagraph>
|
||||
Parameters let you provide arguments to your strategy
|
||||
that it can access for evaluation. Read more in the{' '}
|
||||
<a
|
||||
@ -76,16 +117,15 @@ export const StrategyParameter = ({
|
||||
parameter types documentation
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</StyledParagraph>
|
||||
}
|
||||
/>
|
||||
<div className={styles.nameContainer}>
|
||||
<Input
|
||||
<StyledNameContainer>
|
||||
<StyledNameInput
|
||||
autoFocus
|
||||
label={`Parameter name ${index + 1}*`}
|
||||
onChange={e => set({ name: e.target.value })}
|
||||
value={input.name}
|
||||
className={styles.name}
|
||||
error={Boolean(errors?.[`paramName${index}`])}
|
||||
errorText={errors?.[`paramName${index}`]}
|
||||
/>
|
||||
@ -99,25 +139,23 @@ export const StrategyParameter = ({
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<GeneralSelect
|
||||
</StyledNameContainer>
|
||||
<StyledGeneralSelect
|
||||
label="Type*"
|
||||
name="type"
|
||||
options={paramTypesOptions}
|
||||
value={input.type}
|
||||
onChange={onTypeChange}
|
||||
id={`prop-type-${index}-select`}
|
||||
className={styles.input}
|
||||
/>
|
||||
<Input
|
||||
<StyledDescriptionInput
|
||||
rows={2}
|
||||
multiline
|
||||
label={`Parameter name ${index + 1} description`}
|
||||
onChange={({ target }) => set({ description: target.value })}
|
||||
value={input.description}
|
||||
className={styles.description}
|
||||
/>
|
||||
<FormControlLabel
|
||||
<StyledFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={Boolean(input.required)}
|
||||
@ -125,8 +163,7 @@ export const StrategyParameter = ({
|
||||
/>
|
||||
}
|
||||
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 { TextField, Button } from '@mui/material';
|
||||
import { TextField, Button, styled } from '@mui/material';
|
||||
|
||||
import { useStyles } from './TagTypeForm.styles';
|
||||
import React from 'react';
|
||||
import { trim } from 'component/common/util';
|
||||
import { EDIT } from 'constants/misc';
|
||||
@ -19,6 +18,40 @@ interface ITagTypeForm {
|
||||
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> = ({
|
||||
children,
|
||||
handleSubmit,
|
||||
@ -32,16 +65,13 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
||||
validateNameUniqueness,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<div className={styles.container}>
|
||||
<p className={styles.inputDescription}>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledContainer>
|
||||
<StyledInputDescription>
|
||||
What is your tag name?
|
||||
</p>
|
||||
<Input
|
||||
className={styles.input}
|
||||
</StyledInputDescription>
|
||||
<StyledInput
|
||||
label="Tag name"
|
||||
value={tagName}
|
||||
onChange={e => setTagName(trim(e.target.value))}
|
||||
@ -53,9 +83,10 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<p className={styles.inputDescription}>What is this tag for?</p>
|
||||
<TextField
|
||||
className={styles.input}
|
||||
<StyledInputDescription>
|
||||
What is this tag for?
|
||||
</StyledInputDescription>
|
||||
<StyledTextField
|
||||
label="Tag description"
|
||||
variant="outlined"
|
||||
multiline
|
||||
@ -63,14 +94,14 @@ const TagTypeForm: React.FC<ITagTypeForm> = ({
|
||||
value={tagDesc}
|
||||
onChange={e => setTagDesc(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
</StyledContainer>
|
||||
<StyledButtonContainer>
|
||||
{children}
|
||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
||||
<StyledCancelButton onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledCancelButton>
|
||||
</StyledButtonContainer>
|
||||
</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 classnames from 'classnames';
|
||||
import { Button, styled } from '@mui/material';
|
||||
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PasswordChecker from './PasswordChecker/PasswordChecker';
|
||||
import PasswordMatcher from './PasswordMatcher/PasswordMatcher';
|
||||
import { useStyles } from './ResetPasswordForm.styles';
|
||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||
|
||||
interface IResetPasswordProps {
|
||||
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 { classes: styles } = useStyles();
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPasswordChecker, setShowPasswordChecker] = useState(false);
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
@ -50,13 +61,7 @@ const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
||||
const started = Boolean(password && confirmPassword);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={classnames(
|
||||
themeStyles.contentSpacingY,
|
||||
styles.container
|
||||
)}
|
||||
>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<PasswordField
|
||||
placeholder="Password"
|
||||
value={password || ''}
|
||||
@ -91,17 +96,16 @@ const ResetPasswordForm = ({ onSubmit }: IResetPasswordProps) => {
|
||||
started={started}
|
||||
matchingPasswords={matchingPasswords}
|
||||
/>
|
||||
<Button
|
||||
<StyledButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
data-loading
|
||||
disabled={!submittable}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</StyledButton>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user