1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

chore: remove create/edit feature components

This commit is contained in:
sjaanus 2024-08-22 10:56:22 +03:00
parent 341703978a
commit 4a43d2d956
No known key found for this signature in database
GPG Key ID: 20E007C0248BA7FF
11 changed files with 58 additions and 750 deletions

View File

@ -46,8 +46,6 @@ const BreadcrumbNav = () => {
item !== 'copy' &&
item !== 'features' &&
item !== 'features2' &&
// TODO: this can be removed after new create flag flow goes live
item !== 'create-toggle' &&
item !== 'settings' &&
item !== 'profile' &&
item !== 'insights',

View File

@ -1,93 +0,0 @@
import { screen, waitFor } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import CreateFeature from './CreateFeature';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { Route, Routes } from 'react-router-dom';
const server = testServerSetup();
const setupApi = ({
flagCount,
flagLimit,
}: { flagCount: number; flagLimit: number }) => {
testServerRoute(server, '/api/admin/ui-config', {
flags: {
resourceLimits: true,
},
resourceLimits: {
featureFlags: flagLimit,
},
});
testServerRoute(server, '/api/admin/search/features', {
total: flagCount,
features: Array.from({ length: flagCount }).map((_, i) => ({
name: `flag-${i}`,
})),
});
};
describe('button states', () => {
test("should allow you to create feature flags when you're below the global limit", async () => {
setupApi({ flagLimit: 3, flagCount: 2 });
render(
<Routes>
<Route
path='/projects/:projectId/create-toggle'
element={<CreateFeature />}
/>
</Routes>,
{
route: '/projects/default/create-toggle',
permissions: [{ permission: CREATE_FEATURE }],
},
);
const button = await screen.findByRole('button', {
name: /create feature flag/i,
});
await waitFor(() => {
expect(button).not.toBeDisabled();
});
});
});
describe('limit component', () => {
test('should show limit reached info', async () => {
setupApi({ flagLimit: 1, flagCount: 1 });
render(
<Routes>
<Route
path='/projects/:projectId/create-toggle'
element={<CreateFeature />}
/>
</Routes>,
{
route: '/projects/default/create-toggle',
permissions: [{ permission: CREATE_FEATURE }],
},
);
await screen.findByText('You have reached the limit for feature flags');
});
test('should show approaching limit info', async () => {
setupApi({ flagLimit: 10, flagCount: 9 });
render(
<Routes>
<Route
path='/projects/:projectId/create-toggle'
element={<CreateFeature />}
/>
</Routes>,
{
route: '/projects/default/create-toggle',
permissions: [{ permission: CREATE_FEATURE }],
},
);
await screen.findByText('You are nearing the limit for feature flags');
});
});

View File

@ -1,235 +0,0 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { useNavigate } from 'react-router-dom';
import FeatureForm from '../FeatureForm/FeatureForm';
import useFeatureForm from '../hooks/useFeatureForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { useContext } from 'react';
import { CreateButton } from 'component/common/CreateButton/CreateButton';
import UIContext from 'contexts/UIContext';
import { CF_CREATE_BTN_ID } from 'utils/testIds';
import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate';
import { Alert, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useProjectOverview, {
featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { useUiFlag } from 'hooks/useUiFlag';
import { useGlobalFeatureSearch } from '../FeatureToggleList/useGlobalFeatureSearch';
import { Limit } from 'component/common/Limit/Limit';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(2),
}));
export const isProjectFeatureLimitReached = (
featureLimit: number | null | undefined,
currentFeatureCount: number,
): boolean => {
return (
featureLimit !== null &&
featureLimit !== undefined &&
featureLimit <= currentFeatureCount
);
};
const useGlobalFlagLimit = (flagLimit: number, flagCount: number) => {
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const limitReached = resourceLimitsEnabled && flagCount >= flagLimit;
return {
limitReached,
limitMessage: limitReached
? `You have reached the instance-wide limit of ${flagLimit} feature flags.`
: undefined,
};
};
type FlagLimitsProps = {
global: { limit: number; count: number };
project: { limit?: number; count: number };
};
export const useFlagLimits = ({ global, project }: FlagLimitsProps) => {
const {
limitReached: globalFlagLimitReached,
limitMessage: globalLimitMessage,
} = useGlobalFlagLimit(global.limit, global.count);
const projectFlagLimitReached = isProjectFeatureLimitReached(
project.limit,
project.count,
);
const limitMessage = globalFlagLimitReached
? globalLimitMessage
: projectFlagLimitReached
? `You have reached the project limit of ${project.limit} feature flags.`
: undefined;
return {
limitMessage,
globalFlagLimitReached,
projectFlagLimitReached,
};
};
const CreateFeature = () => {
const { setToastData, setToastApiError } = useToast();
const { setShowFeedback } = useContext(UIContext);
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
const {
type,
setType,
name,
setName,
project,
setProject,
description,
setDescription,
validateToggleName,
impressionData,
setImpressionData,
getTogglePayload,
clearErrors,
errors,
} = useFeatureForm();
const { project: projectInfo } = useProjectOverview(project);
const { createFeatureToggle, loading } = useFeatureApi();
const { total: totalFlags, loading: loadingTotalFlagCount } =
useGlobalFeatureSearch();
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const { globalFlagLimitReached, projectFlagLimitReached, limitMessage } =
useFlagLimits({
global: {
limit: uiConfig.resourceLimits.featureFlags,
count: totalFlags ?? 0,
},
project: {
limit: projectInfo.featureLimit,
count: featuresCount(projectInfo),
},
});
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const validToggleName = await validateToggleName();
if (validToggleName) {
const payload = getTogglePayload();
try {
await createFeatureToggle(project, payload);
navigate(`/projects/${project}/features/${name}`, {
replace: true,
});
setToastData({
title: 'Flag created successfully',
text: 'Now you can start using your flag.',
confetti: true,
type: 'success',
});
setShowFeedback(true);
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
}
};
const formatApiCode = () => {
return `curl --location --request POST '${
uiConfig.unleashUrl
}/api/admin/projects/${project}/features' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getTogglePayload(), undefined, 2)}'`;
};
const handleCancel = () => {
navigate(GO_BACK);
};
return (
<FormTemplate
loading={loading}
title='Create feature flag'
description='Feature flags support different use cases, each with their own specific needs such as simple static routing or more complex routing.
The feature flag is disabled when created and you decide when to enable'
documentationLink='https://docs.getunleash.io/reference/feature-toggle-types'
documentationLinkLabel='Feature flag types documentation'
formatApiCode={formatApiCode}
>
<ConditionallyRender
condition={projectFlagLimitReached}
show={
<StyledAlert severity='error'>
<strong>Feature flag project limit reached. </strong> To
be able to create more feature flags in this project
please increase the feature flag upper limit in the
project settings.
</StyledAlert>
}
/>
<FeatureForm
type={type}
name={name}
project={project}
description={description}
setType={setType}
setName={setName}
setProject={setProject}
setDescription={setDescription}
validateToggleName={validateToggleName}
setImpressionData={setImpressionData}
impressionData={impressionData}
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
mode='Create'
clearErrors={clearErrors}
featureNaming={projectInfo.featureNaming}
Limit={
<ConditionallyRender
condition={resourceLimitsEnabled}
show={
<Limit
name='feature flags'
limit={uiConfig.resourceLimits.featureFlags}
currentValue={totalFlags ?? 0}
/>
}
/>
}
>
<CreateButton
name='feature flag'
disabled={
loadingTotalFlagCount ||
globalFlagLimitReached ||
projectFlagLimitReached
}
permission={CREATE_FEATURE}
projectId={project}
data-testid={CF_CREATE_BTN_ID}
tooltipProps={{
title: limitMessage,
arrow: true,
}}
/>
</FeatureForm>
</FormTemplate>
);
};
export default CreateFeature;

View File

@ -1,114 +0,0 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { useNavigate } from 'react-router-dom';
import FeatureForm from '../FeatureForm/FeatureForm';
import useFeatureForm from '../hooks/useFeatureForm';
import * as jsonpatch from 'fast-json-patch';
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { GO_BACK } from 'constants/navigate';
const EditFeature = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
const { patchFeatureToggle: patchFeatureFlag, loading } = useFeatureApi();
const { feature } = useFeature(projectId, featureId);
const {
type,
setType,
name,
setName,
project,
setProject,
description,
setDescription,
impressionData,
setImpressionData,
clearErrors,
errors,
} = useFeatureForm(
feature?.name,
feature?.type,
feature?.project,
feature?.description,
feature?.impressionData,
);
const createPatch = () => {
const comparison = { ...feature, type, description, impressionData };
const patch = jsonpatch.compare(feature, comparison);
return patch;
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const patch = createPatch();
try {
await patchFeatureFlag(project, featureId, patch);
navigate(`/projects/${project}/features/${name}`);
setToastData({
title: 'Flag updated successfully',
type: 'success',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
const formatApiCode = () => {
return `curl --location --request PATCH '${
uiConfig.unleashUrl
}/api/admin/projects/${projectId}/features/${featureId}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(createPatch(), undefined, 2)}'`;
};
const handleCancel = () => {
navigate(GO_BACK);
};
return (
<FormTemplate
loading={loading}
title='Edit Feature flag'
description='Feature flags support different use cases, each with their own specific needs such as simple static routing or more complex routing.
The feature flag is disabled when created and you decide when to enable'
documentationLink='https://docs.getunleash.io/reference/feature-toggle-types'
documentationLinkLabel='Feature flag types documentation'
formatApiCode={formatApiCode}
>
<FeatureForm
type={type}
name={name}
project={project}
description={description}
setType={setType}
setName={setName}
setProject={setProject}
setDescription={setDescription}
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
impressionData={impressionData}
setImpressionData={setImpressionData}
mode='Edit'
clearErrors={clearErrors}
>
<UpdateButton permission={UPDATE_FEATURE} projectId={project} />
</FeatureForm>
</FormTemplate>
);
};
export default EditFeature;

View File

@ -1,279 +0,0 @@
import {
Button,
FormControl,
FormControlLabel,
styled,
Switch,
type Theme,
Typography,
Link,
Box,
} from '@mui/material';
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
import KeyboardArrowDownOutlined from '@mui/icons-material/KeyboardArrowDownOutlined';
import { projectFilterGenerator } from 'utils/projectFilterGenerator';
import FeatureProjectSelect from '../FeatureView/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { trim } from 'component/common/util';
import Input from 'component/common/Input/Input';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { useNavigate } from 'react-router-dom';
import React from 'react';
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
import type { FeatureNamingType } from 'interfaces/project';
import { FeatureNamingPatternInfo } from '../FeatureNamingPatternInfo/FeatureNamingPatternInfo';
import type { CreateFeatureSchemaType } from 'openapi';
interface IFeatureToggleForm {
type: CreateFeatureSchemaType;
name: string;
description: string;
project: string;
impressionData: boolean;
setType: React.Dispatch<React.SetStateAction<CreateFeatureSchemaType>>;
setName: React.Dispatch<React.SetStateAction<string>>;
setDescription: React.Dispatch<React.SetStateAction<string>>;
setProject: React.Dispatch<React.SetStateAction<string>>;
setImpressionData: React.Dispatch<React.SetStateAction<boolean>>;
featureNaming?: FeatureNamingType;
validateToggleName?: () => void;
handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string };
mode: 'Create' | 'Edit';
clearErrors: () => void;
children?: React.ReactNode;
Limit?: React.ReactNode;
}
const StyledForm = styled('form')({
height: '100%',
});
const StyledInputDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1),
}));
const StyledInput = styled(Input)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
const StyledFormControl = styled(FormControl)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
const styledSelectInput = (theme: Theme) => ({
marginBottom: theme.spacing(2),
minWidth: '400px',
[theme.breakpoints.down('sm')]: {
minWidth: '379px',
},
});
const StyledTypeDescription = styled('p')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.secondary,
top: '-13px',
position: 'relative',
}));
const StyledButtonContainer = styled('div')({
display: 'flex',
justifyContent: 'flex-end',
});
const StyledRow = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
marginTop: theme.spacing(1),
}));
const StyledCancelButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(3),
}));
const styledTypography = (theme: Theme) => ({
margin: theme.spacing(1, 0),
});
const LimitContainer = styled(Box)(({ theme }) => ({
'&:has(*)': {
marginBottom: theme.spacing(2),
},
}));
const FeatureForm: React.FC<IFeatureToggleForm> = ({
children,
type,
name,
description,
project,
setType,
setName,
setDescription,
setProject,
validateToggleName,
featureNaming,
setImpressionData,
impressionData,
handleSubmit,
handleCancel,
errors,
mode,
clearErrors,
Limit,
}) => {
const { featureTypes } = useFeatureTypes();
const navigate = useNavigate();
const { permissions } = useAuthPermissions();
const editable = mode !== 'Edit';
const renderToggleDescription = () => {
return featureTypes.find((flag) => flag.id === type)?.description;
};
const displayFeatureNamingInfo = Boolean(featureNaming?.pattern);
React.useEffect(() => {
if (featureNaming?.pattern && validateToggleName && name) {
clearErrors();
validateToggleName();
}
}, [featureNaming?.pattern]);
return (
<StyledForm onSubmit={handleSubmit}>
<StyledInputDescription>
What would you like to call your flag?
</StyledInputDescription>
<ConditionallyRender
condition={displayFeatureNamingInfo}
show={
<FeatureNamingPatternInfo featureNaming={featureNaming!} />
}
/>
<StyledInput
autoFocus
disabled={mode === 'Edit'}
label='Name'
aria-details={
displayFeatureNamingInfo
? 'feature-naming-pattern-info'
: undefined
}
id='feature-flag-name'
error={Boolean(errors.name)}
errorText={errors.name}
onFocus={() => clearErrors()}
value={name}
onChange={(e) => setName(trim(e.target.value))}
data-testid={CF_NAME_ID}
onBlur={validateToggleName}
/>
<StyledInputDescription>
What kind of feature flag do you want?
</StyledInputDescription>
<FeatureTypeSelect
sx={styledSelectInput}
value={type}
onChange={setType}
label={'Flag type'}
id='feature-type-select'
editable
data-testid={CF_TYPE_ID}
IconComponent={KeyboardArrowDownOutlined}
/>
<StyledTypeDescription>
{renderToggleDescription()}
</StyledTypeDescription>
<ConditionallyRender
condition={editable}
show={
<StyledInputDescription>
In which project do you want to save the flag?
</StyledInputDescription>
}
/>
{/* TODO: this can be removed after new create flag flow goes live */}
<FeatureProjectSelect
value={project}
onChange={(projectId) => {
setProject(projectId);
navigate(`/projects/${projectId}/create-toggle`, {
replace: true,
});
}}
enabled={editable}
filter={projectFilterGenerator(
permissions || [],
CREATE_FEATURE,
)}
IconComponent={KeyboardArrowDownOutlined}
sx={styledSelectInput}
/>
<StyledInputDescription>
How would you describe your feature flag?
</StyledInputDescription>
<StyledInput
multiline
rows={4}
label='Description'
placeholder='A short description of the feature flag'
value={description}
data-testid={CF_DESC_ID}
onChange={(e) => setDescription(e.target.value)}
/>
<StyledFormControl>
<Typography
variant='subtitle1'
sx={styledTypography}
data-loading
component='h2'
>
Impression Data
</Typography>
<p>
When you enable impression data for a feature flag, your
client SDKs will emit events you can listen for every time
this flag gets triggered. Learn more in{' '}
<Link
target='_blank'
rel='noopener noreferrer'
href='https://docs.getunleash.io/advanced/impression_data'
>
the impression data documentation
</Link>
</p>
<StyledRow>
<FormControlLabel
labelPlacement='start'
style={{ marginLeft: 0 }}
control={
<Switch
name='impressionData'
onChange={() =>
setImpressionData(!impressionData)
}
checked={impressionData}
/>
}
label='Enable impression data'
/>
</StyledRow>
</StyledFormControl>
<LimitContainer>{Limit}</LimitContainer>
<StyledButtonContainer>
{children}
<StyledCancelButton onClick={handleCancel}>
Cancel
</StyledCancelButton>
</StyledButtonContainer>
</StyledForm>
);
};
export default FeatureForm;

View File

@ -16,8 +16,6 @@ import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironm
import { EditContext } from 'component/context/EditContext/EditContext';
import EditTagType from 'component/tags/EditTagType/EditTagType';
import CreateTagType from 'component/tags/CreateTagType/CreateTagType';
import CreateFeature from 'component/feature/CreateFeature/CreateFeature';
import EditFeature from 'component/feature/EditFeature/EditFeature';
import ContextList from 'component/context/ContextList/ContextList/ContextList';
import { CreateIntegration } from 'component/integrations/CreateIntegration/CreateIntegration';
import { EditIntegration } from 'component/integrations/EditIntegration/EditIntegration';
@ -86,14 +84,6 @@ export const routes: IRoute[] = [
type: 'protected',
menu: {},
},
{
path: '/projects/:projectId/features/:featureId/edit',
parent: '/projects',
title: 'Edit feature',
component: EditFeature,
type: 'protected',
menu: {},
},
{
path: '/projects/:projectId/features/:featureId/*',
parent: '/projects',
@ -102,14 +92,6 @@ export const routes: IRoute[] = [
type: 'protected',
menu: {},
},
{
path: '/projects/:projectId/create-toggle',
parent: '/projects/:projectId/features',
title: 'Create feature flag',
component: CreateFeature,
type: 'protected',
menu: {},
},
{
path: '/projects/:projectId/*',
parent: '/projects',

View File

@ -21,7 +21,6 @@ import useFeatureForm from 'component/feature/hooks/useFeatureForm';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import FlagIcon from '@mui/icons-material/Flag';
import ImpressionDataIcon from '@mui/icons-material/AltRoute';
import { useFlagLimits } from 'component/feature/CreateFeature/CreateFeature';
import { useGlobalFeatureSearch } from 'component/feature/FeatureToggleList/useGlobalFeatureSearch';
import useProjectOverview, {
featuresCount,
@ -37,6 +36,7 @@ import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { MultiSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton';
import type { ITag } from 'interfaces/tags';
import { ToggleConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/ToggleConfigButton';
import { useFlagLimits } from './useFlagLimits';
interface ICreateFeatureDialogProps {
open: boolean;

View File

@ -1,4 +1,4 @@
import { isProjectFeatureLimitReached } from './CreateFeature';
import { isProjectFeatureLimitReached } from './useFlagLimits';
test('isFeatureLimitReached should return false when featureLimit is null', async () => {
expect(isProjectFeatureLimitReached(null, 5)).toBe(false);

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react';
import { useFlagLimits } from './CreateFeature';
import { vi } from 'vitest';
import { useFlagLimits } from './useFlagLimits';
vi.mock('hooks/useUiFlag', async (importOriginal) => {
const actual = await importOriginal();

View File

@ -0,0 +1,53 @@
import { useUiFlag } from 'hooks/useUiFlag';
type FlagLimitsProps = {
global: { limit: number; count: number };
project: { limit?: number; count: number };
};
export const useFlagLimits = ({ global, project }: FlagLimitsProps) => {
const {
limitReached: globalFlagLimitReached,
limitMessage: globalLimitMessage,
} = useGlobalFlagLimit(global.limit, global.count);
const projectFlagLimitReached = isProjectFeatureLimitReached(
project.limit,
project.count,
);
const limitMessage = globalFlagLimitReached
? globalLimitMessage
: projectFlagLimitReached
? `You have reached the project limit of ${project.limit} feature flags.`
: undefined;
return {
limitMessage,
globalFlagLimitReached,
projectFlagLimitReached,
};
};
const useGlobalFlagLimit = (flagLimit: number, flagCount: number) => {
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const limitReached = resourceLimitsEnabled && flagCount >= flagLimit;
return {
limitReached,
limitMessage: limitReached
? `You have reached the instance-wide limit of ${flagLimit} feature flags.`
: undefined,
};
};
export const isProjectFeatureLimitReached = (
featureLimit: number | null | undefined,
currentFeatureCount: number,
): boolean => {
return (
featureLimit !== null &&
featureLimit !== undefined &&
featureLimit <= currentFeatureCount
);
};

View File

@ -6,7 +6,7 @@ export const getCreateTogglePath = (
projectId: string,
query?: Record<string, string>,
) => {
const path = `/projects/${projectId}/create-toggle`;
const path = `/projects/${projectId}?create=true`;
let queryString: string | undefined;
if (query) {
@ -16,12 +16,8 @@ export const getCreateTogglePath = (
}
if (queryString) {
return `${path}?${queryString}`;
return `${path}&${queryString}`;
}
return path;
};
export const getProjectEditPath = (projectId: string) => {
return `/projects/${projectId}/settings`;
};