1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00

Fix/constraint card adjustments (#1154)

* style fixes

* Constraint card styling adjustments

* Style Fixes

* lint and fmt

* lint and fmt

* Changed the way the expandable property is evaluated to use the text.length

Co-authored-by: Tymoteusz Czech <tymek+gpg@getunleash.ai>
This commit is contained in:
andreas-unleash 2022-07-27 14:50:47 +03:00 committed by GitHub
parent c70b38a62a
commit c3e9b49e12
20 changed files with 121 additions and 103 deletions

View File

@ -12,6 +12,9 @@ export const useStyles = makeStyles()(theme => ({
marginBottom: '1rem', marginBottom: '1rem',
marginRight: 0, marginRight: 0,
}, },
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '8px',
},
}, },
constraintIcon: { constraintIcon: {
fill: '#fff', fill: '#fff',
@ -59,6 +62,7 @@ export const useStyles = makeStyles()(theme => ({
}, },
headerValuesExpand: { headerValuesExpand: {
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
marginTop: '4px',
color: theme.palette.primary.dark, color: theme.palette.primary.dark,
[theme.breakpoints.down(710)]: { [theme.breakpoints.down(710)]: {
textAlign: 'center', textAlign: 'center',
@ -71,6 +75,10 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(650)]: { [theme.breakpoints.down(650)]: {
paddingRight: 0, paddingRight: 0,
}, },
[theme.breakpoints.between(1101, 1365)]: {
minWidth: '152px',
paddingRight: '0.5rem',
},
}, },
editingBadge: { editingBadge: {
borderRadius: theme.shape.borderRadiusExtraLarge, borderRadius: theme.shape.borderRadiusExtraLarge,
@ -108,6 +116,10 @@ export const useStyles = makeStyles()(theme => ({
headerSelect: { headerSelect: {
marginRight: '1rem', marginRight: '1rem',
width: '200px', width: '200px',
[theme.breakpoints.between(1101, 1365)]: {
width: '170px',
marginRight: '8px',
},
}, },
chip: { chip: {
margin: '0 0.5rem 0.5rem 0', margin: '0 0.5rem 0.5rem 0',

View File

@ -5,6 +5,7 @@ export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
width: '28px', width: '28px',
minWidth: '28px', minWidth: '28px',
maxWidth: '28px', maxWidth: '28px',
height: 'auto',
backgroundColor: theme.palette.tertiary.background, backgroundColor: theme.palette.tertiary.background,
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
padding: '0 1px 0', padding: '0 1px 0',
@ -12,6 +13,10 @@ export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
'&:hover': { '&:hover': {
background: theme.palette.tertiary.contrast[300], background: theme.palette.tertiary.contrast[300],
}, },
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '0.5rem',
alignItems: 'center',
},
})); }));
export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({ export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
@ -27,4 +32,8 @@ export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
color: theme.palette.primary.contrastText, color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
}, },
[theme.breakpoints.between(1101, 1365)]: {
marginRight: '0.5rem',
alignItems: 'center',
},
})); }));

View File

@ -11,12 +11,18 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(860)]: { [theme.breakpoints.down(860)]: {
display: 'none', display: 'none',
}, },
marginLeft: '0.75rem', },
helpWrapper: {
marginLeft: '12px',
height: '24px',
}, },
addCustomLabel: { addCustomLabel: {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'start', justifyContent: 'start',
margin: '0.75rem 0', margin: '0.75rem 0 ',
},
customConstraintLabel: {
color: theme.palette.text.secondary,
}, },
})); }));

View File

@ -1,6 +1,6 @@
import React, { forwardRef, Fragment, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import { Button, Tooltip } from '@mui/material'; import { Button, Tooltip } from '@mui/material';
import { Help } from '@mui/icons-material'; import { HelpOutline } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordion } from 'component/common/ConstraintAccordion/ConstraintAccordion'; import { ConstraintAccordion } from 'component/common/ConstraintAccordion/ConstraintAccordion';
import produce from 'immer'; import produce from 'immer';
@ -10,7 +10,6 @@ import { objectId } from 'utils/objectId';
import { useStyles } from './ConstraintAccordionList.styles'; import { useStyles } from './ConstraintAccordionList.styles';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IConstraintAccordionListProps { interface IConstraintAccordionListProps {
constraints: IConstraint[]; constraints: IConstraint[];
@ -100,13 +99,37 @@ export const ConstraintAccordionList = forwardRef<
return ( return (
<div className={styles.container} id={constraintAccordionListId}> <div className={styles.container} id={constraintAccordionListId}>
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<p className={styles.customConstraintLabel}>
Custom constraints
</p>
}
/>
{constraints.map((constraint, index) => (
<ConstraintAccordion
key={objectId(constraint)}
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/>
))}
<ConditionallyRender <ConditionallyRender
condition={Boolean(showCreateButton && onAdd)} condition={Boolean(showCreateButton && onAdd)}
show={ show={
<div> <div>
<div className={styles.addCustomLabel}> <div className={styles.addCustomLabel}>
<p>Add any number of custom constraints</p> <p>Add any number of custom constraints</p>
<Tooltip title="Help" arrow> <Tooltip
title="Help"
arrow
className={styles.helpWrapper}
>
<a <a
href={ href={
'https://docs.getunleash.io/advanced/strategy_constraints' 'https://docs.getunleash.io/advanced/strategy_constraints'
@ -114,7 +137,7 @@ export const ConstraintAccordionList = forwardRef<
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<Help className={styles.help} /> <HelpOutline className={styles.help} />
</a> </a>
</Tooltip> </Tooltip>
</div> </div>
@ -123,30 +146,13 @@ export const ConstraintAccordionList = forwardRef<
onClick={onAdd} onClick={onAdd}
variant="outlined" variant="outlined"
color="secondary" color="secondary"
sx={{ mb: 2 }}
> >
Add custom constraint Add custom constraint
</Button> </Button>
</div> </div>
} }
/> />
{constraints.map((constraint, index) => (
<Fragment key={`${constraint.contextName}-${index}`}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="AND" />}
/>
<ConstraintAccordion
key={objectId(constraint)}
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/>
</Fragment>
))}
</div> </div>
); );
}); });

View File

@ -42,12 +42,17 @@ export const ConstraintAccordionView = ({
className={styles.accordion} className={styles.accordion}
classes={{ root: styles.accordionRoot }} classes={{ root: styles.accordionRoot }}
expanded={expanded} expanded={expanded}
sx={{ cursor: expandable ? 'pointer' : 'default' }}
> >
<AccordionSummary <AccordionSummary
classes={{ root: styles.summary }} classes={{ root: styles.summary }}
expandIcon={null} expandIcon={null}
onClick={handleClick} onClick={handleClick}
sx={{
cursor: expandable ? 'pointer' : 'default!important',
'&:hover': {
cursor: expandable ? 'pointer' : 'default!important',
},
}}
> >
<ConstraintAccordionViewHeader <ConstraintAccordionViewHeader
constraint={constraint} constraint={constraint}

View File

@ -1,12 +1,7 @@
import { ImportExportOutlined, TextFormatOutlined } from '@mui/icons-material';
import { stringOperators } from 'constants/operators';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { oneOf } from 'utils/oneOf'; import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { formatConstraintValue } from 'utils/formatConstraintValue'; import { formatConstraintValue } from 'utils/formatConstraintValue';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { useLocationSettings } from 'hooks/useLocationSettings';
import { useStyles as useAccordionStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { useStyles } from './ConstraintAccordionViewBody.style';
import { MultipleValues } from './MultipleValues/MultipleValues'; import { MultipleValues } from './MultipleValues/MultipleValues';
import { SingleValue } from './SingleValue/SingleValue'; import { SingleValue } from './SingleValue/SingleValue';
@ -17,35 +12,11 @@ interface IConstraintAccordionViewBodyProps {
export const ConstraintAccordionViewBody = ({ export const ConstraintAccordionViewBody = ({
constraint, constraint,
}: IConstraintAccordionViewBodyProps) => { }: IConstraintAccordionViewBodyProps) => {
const { classes: styles } = useAccordionStyles(); const { classes: styles } = useStyles();
const { classes } = useStyles();
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
return ( return (
<div> <div>
<ConditionallyRender
condition={
oneOf(stringOperators, constraint.operator) &&
Boolean(constraint.caseInsensitive)
}
show={
<p className={classes.settingsParagraph}>
<TextFormatOutlined className={styles.settingsIcon} />{' '}
Case insensitive setting is active
</p>
}
/>
<ConditionallyRender
condition={Boolean(constraint.inverted)}
show={
<p className={classes.settingsParagraph}>
<ImportExportOutlined className={styles.settingsIcon} />{' '}
Operator is negated
</p>
}
/>
<div className={styles.valuesContainer}> <div className={styles.valuesContainer}>
<MultipleValues values={constraint.values} /> <MultipleValues values={constraint.values} />
<SingleValue <SingleValue

View File

@ -1,7 +1,7 @@
import { styled, Tooltip } from '@mui/material'; import { styled, Tooltip } from '@mui/material';
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator'; import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { ContraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue'; import { ConstraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ConstraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues'; import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
import React from 'react'; import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy'; import { IConstraint } from '../../../../../../interfaces/strategy';
@ -50,8 +50,9 @@ export const ConstraintAccordionViewHeaderInfo = ({
<ConditionallyRender <ConditionallyRender
condition={singleValue} condition={singleValue}
show={ show={
<ContraintAccordionViewHeaderSingleValue <ConstraintAccordionViewHeaderSingleValue
constraint={constraint} constraint={constraint}
allowExpand={allowExpand}
/> />
} }
elseShow={ elseShow={
@ -59,6 +60,7 @@ export const ConstraintAccordionViewHeaderInfo = ({
constraint={constraint} constraint={constraint}
expanded={expanded} expanded={expanded}
allowExpand={allowExpand} allowExpand={allowExpand}
maxLength={112}
/> />
} }
/> />

View File

@ -3,12 +3,11 @@ import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators'; import { stringOperators } from '../../../../../../constants/operators';
import { styled, Tooltip } from '@mui/material'; import { styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg'; import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper'; import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
import { IConstraint } from '../../../../../../interfaces/strategy'; import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles'; import { useStyles } from '../../../ConstraintAccordion.styles';
import { getTextWidth } from '../../../helpers';
const StyledValuesSpan = styled('span')(({ theme }) => ({ const StyledValuesSpan = styled('span')(({ theme }) => ({
display: '-webkit-box', display: '-webkit-box',
@ -27,6 +26,7 @@ const StyledValuesSpan = styled('span')(({ theme }) => ({
interface ConstraintSingleValueProps { interface ConstraintSingleValueProps {
constraint: IConstraint; constraint: IConstraint;
expanded: boolean; expanded: boolean;
maxLength: number;
allowExpand: (shouldExpand: boolean) => void; allowExpand: (shouldExpand: boolean) => void;
} }
@ -34,33 +34,22 @@ export const ConstraintAccordionViewHeaderMultipleValues = ({
constraint, constraint,
expanded, expanded,
allowExpand, allowExpand,
maxLength,
}: ConstraintSingleValueProps) => { }: ConstraintSingleValueProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const elementRef = useRef<HTMLElement>(null);
const [width, setWidth] = useState(0);
const [textWidth, setTextWidth] = useState(0);
const [expandable, setExpandable] = useState(false); const [expandable, setExpandable] = useState(false);
useEffect(() => { const text = useMemo(() => {
if (elementRef && elementRef.current != null) { return constraint?.values?.map(value => value).join(', ');
setTextWidth( }, [constraint]);
Math.round(getTextWidth(elementRef.current.innerText) / 2) // 2 lines
);
setWidth(elementRef.current.clientWidth);
}
}, []);
useEffect(() => { useEffect(() => {
if (textWidth && width) { if (text) {
setExpandable(textWidth > width); allowExpand((text?.length ?? 0) > maxLength);
setExpandable((text?.length ?? 0) > maxLength);
} }
}, [textWidth, width]); }, [text, maxLength, allowExpand, setExpandable]);
useEffect(() => {
allowExpand(expandable);
}, [expandable, allowExpand]);
return ( return (
<div className={styles.headerValuesContainerWrapper}> <div className={styles.headerValuesContainerWrapper}>
@ -78,9 +67,7 @@ export const ConstraintAccordionViewHeaderMultipleValues = ({
} }
/> />
<div className={styles.headerValuesContainer}> <div className={styles.headerValuesContainer}>
<StyledValuesSpan ref={elementRef}> <StyledValuesSpan>{text}</StyledValuesSpan>
{constraint?.values?.map(value => value).join(', ')}
</StyledValuesSpan>
<ConditionallyRender <ConditionallyRender
condition={expandable} condition={expandable}
show={ show={

View File

@ -1,3 +1,4 @@
import React, { useEffect } from 'react';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf'; import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators'; import { stringOperators } from '../../../../../../constants/operators';
@ -18,14 +19,20 @@ const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
interface ConstraintSingleValueProps { interface ConstraintSingleValueProps {
constraint: IConstraint; constraint: IConstraint;
allowExpand: (shouldExpand: boolean) => void;
} }
export const ContraintAccordionViewHeaderSingleValue = ({ export const ConstraintAccordionViewHeaderSingleValue = ({
constraint, constraint,
allowExpand,
}: ConstraintSingleValueProps) => { }: ConstraintSingleValueProps) => {
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
useEffect(() => {
allowExpand(false);
}, [allowExpand]);
return ( return (
<div className={styles.headerValuesContainerWrapper}> <div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender <ConditionallyRender

View File

@ -1,6 +1,6 @@
import { forwardRef, ReactNode } from 'react';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FC, forwardRef } from 'react';
export const StyledIconWrapperBase = styled('div')<{ export const StyledIconWrapperBase = styled('div')<{
prefix?: boolean; prefix?: boolean;
@ -24,7 +24,7 @@ const StyledPrefixIconWrapper = styled(StyledIconWrapperBase)(() => ({
export const StyledIconWrapper = forwardRef< export const StyledIconWrapper = forwardRef<
HTMLDivElement, HTMLDivElement,
{ isPrefix?: boolean; children?: React.ReactNode } { isPrefix?: boolean; children?: ReactNode }
>(({ isPrefix, ...props }, ref) => ( >(({ isPrefix, ...props }, ref) => (
<ConditionallyRender <ConditionallyRender
condition={Boolean(isPrefix)} condition={Boolean(isPrefix)}

View File

@ -34,4 +34,10 @@ export const useStyles = makeStyles()(theme => ({
borderTopColor: theme.palette.grey[300], borderTopColor: theme.palette.grey[300],
}, },
}, },
formInput: {
[theme.breakpoints.between(1101, 1365)]: {
width: '170px',
marginRight: '8px',
},
},
})); }));

View File

@ -48,7 +48,12 @@ export const ConstraintOperatorSelect = ({
}; };
return ( return (
<FormControl variant="outlined" size="small" fullWidth> <FormControl
variant="outlined"
size="small"
fullWidth
className={styles.formInput}
>
<InputLabel htmlFor="operator-select">Operator</InputLabel> <InputLabel htmlFor="operator-select">Operator</InputLabel>
<Select <Select
id="operator-select" id="operator-select"

View File

@ -1,13 +0,0 @@
export function getTextWidth(text: string | null) {
if (text != null) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context != null) {
context.font = getComputedStyle(document.body).font;
return context.measureText(text).width;
}
}
return 0;
}

View File

@ -27,6 +27,7 @@ export const useStyles = makeStyles()(theme => ({
width: formTemplateSidebarWidth, width: formTemplateSidebarWidth,
[theme.breakpoints.down(1100)]: { [theme.breakpoints.down(1100)]: {
width: '100%', width: '100%',
color: 'red',
}, },
[theme.breakpoints.down(500)]: { [theme.breakpoints.down(500)]: {
padding: '2rem 1rem', padding: '2rem 1rem',

View File

@ -7,7 +7,7 @@ export const useStyles = makeStyles()(() => ({
right: 0, right: 0,
bottom: 0, bottom: 0,
height: '100vh', height: '100vh',
maxWidth: '90vw', maxWidth: '98vw',
width: 1300, width: 1300,
overflow: 'auto', overflow: 'auto',
boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)', boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)',

View File

@ -6,4 +6,7 @@ export const useStyles = makeStyles()(theme => ({
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,
fontWeight: theme.fontWeight.bold, fontWeight: theme.fontWeight.bold,
}, },
divider: {
border: `1px dashed ${theme.palette.divider}`,
},
})); }));

View File

@ -9,6 +9,7 @@ import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/Fe
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/SegmentDocs';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
import { Divider } from '@mui/material';
interface IFeatureStrategySegmentProps { interface IFeatureStrategySegmentProps {
segments: ISegment[]; segments: ISegment[];
@ -65,6 +66,7 @@ export const FeatureStrategySegment = ({
segments={selectedSegments} segments={selectedSegments}
setSegments={setSelectedSegments} setSegments={setSelectedSegments}
/> />
<Divider className={styles.divider} />
</> </>
); );
}; };

View File

@ -22,4 +22,7 @@ export const useStyles = makeStyles()(theme => ({
alignItems: 'center', alignItems: 'center',
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
}, },
selectedSegmentsLabel: {
color: theme.palette.text.secondary,
},
})); }));

View File

@ -24,6 +24,14 @@ export const FeatureStrategySegmentList = ({
return ( return (
<> <>
<ConditionallyRender
condition={segments && segments.length > 0}
show={
<p className={styles.selectedSegmentsLabel}>
Selected Segments
</p>
}
/>
<div className={styles.list}> <div className={styles.list}>
{segments.map((segment, i) => ( {segments.map((segment, i) => (
<Fragment key={segment.id}> <Fragment key={segment.id}>

View File

@ -23,8 +23,6 @@ import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { IUser } from 'interfaces/user'; import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group'; import { IGroup } from 'interfaces/group';
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectRoleDescription } from './ProjectRoleDescription/ProjectRoleDescription'; import { ProjectRoleDescription } from './ProjectRoleDescription/ProjectRoleDescription';
import { useAccess } from '../../../../hooks/api/getters/useAccess/useAccess'; import { useAccess } from '../../../../hooks/api/getters/useAccess/useAccess';