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

feat: add create and edit screen for tag-types (NEW) (#603)

* feat: add create and edit screen for tag-types

* feat: update Edit and create component with permissions

* refactor: add TagForm type to react FC

* fix: routes

* fix: add edit button

* fix: update snapshot

* fix: update permission

* fix: permission

Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
Youssef Khedher 2022-01-18 14:35:50 +01:00 committed by GitHub
parent 80e80805f7
commit 7baf8400ca
17 changed files with 579 additions and 118 deletions

View File

@ -263,15 +263,6 @@ Array [
"title": "Tag types", "title": "Tag types",
"type": "protected", "type": "protected",
}, },
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/tags",
"path": "/tags/create",
"title": "Create",
"type": "protected",
},
Object { Object {
"component": [Function], "component": [Function],
"layout": "main", "layout": "main",

View File

@ -15,10 +15,6 @@ import CreateContextField from '../../page/context/create';
import EditContextField from '../../page/context/edit'; import EditContextField from '../../page/context/edit';
import CreateProject from '../../page/project/create'; import CreateProject from '../../page/project/create';
import ListTagTypes from '../../page/tag-types'; import ListTagTypes from '../../page/tag-types';
import CreateTagType from '../../page/tag-types/create';
import EditTagType from '../../page/tag-types/edit';
import ListTags from '../../page/tags';
import CreateTag from '../../page/tags/create';
import Addons from '../../page/addons'; import Addons from '../../page/addons';
import AddonsCreate from '../../page/addons/create'; import AddonsCreate from '../../page/addons/create';
import AddonsEdit from '../../page/addons/edit'; import AddonsEdit from '../../page/addons/edit';
@ -48,6 +44,8 @@ import CreateApiToken from '../admin/api-token/CreateApiToken/CreateApiToken';
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment'; import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
import EditEnvironment from '../environments/EditEnvironment/EditEnvironment'; import EditEnvironment from '../environments/EditEnvironment/EditEnvironment';
import EditTagType from '../tagTypes/EditTagType/EditTagType';
import CreateTagType from '../tagTypes/CreateTagType/CreateTagType';
export const routes = [ export const routes = [
// Project // Project
@ -304,24 +302,6 @@ export const routes = [
layout: 'main', layout: 'main',
menu: { mobile: true, advanced: true }, menu: { mobile: true, advanced: true },
}, },
{
path: '/tags/create',
parent: '/tags',
title: 'Create',
component: CreateTag,
type: 'protected',
layout: 'main',
menu: {},
},
{
path: '/tags',
title: 'Tags',
component: ListTags,
hidden: true,
type: 'protected',
layout: 'main',
menu: {},
},
// Addons // Addons
{ {

View File

@ -13,7 +13,6 @@ export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
export const CREATE_PROJECT = 'CREATE_PROJECT'; export const CREATE_PROJECT = 'CREATE_PROJECT';
export const UPDATE_PROJECT = 'UPDATE_PROJECT'; export const UPDATE_PROJECT = 'UPDATE_PROJECT';
export const DELETE_PROJECT = 'DELETE_PROJECT'; export const DELETE_PROJECT = 'DELETE_PROJECT';
export const CREATE_TAG_TYPE = 'CREATE_TAG_TYPE';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE'; export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE'; export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
export const CREATE_TAG = 'CREATE_TAG'; export const CREATE_TAG = 'CREATE_TAG';

View File

@ -2,6 +2,10 @@
min-width: 100px; min-width: 100px;
} }
.icon {
fill: #757575;
}
.textfield { .textfield {
margin-left: 15px; margin-left: 15px;
} }

View File

@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from 'react'; import { useContext, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { import {
List, List,
ListItem, ListItem,
@ -11,38 +10,53 @@ import {
Button, Button,
Tooltip, Tooltip,
} from '@material-ui/core'; } from '@material-ui/core';
import { Add, Delete, Label } from '@material-ui/icons'; import { Add, Delete, Edit, Label } from '@material-ui/icons';
import HeaderTitle from '../../common/HeaderTitle'; import HeaderTitle from '../../common/HeaderTitle';
import PageContent from '../../common/PageContent/PageContent'; import PageContent from '../../common/PageContent/PageContent';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import { import {
CREATE_TAG_TYPE,
DELETE_TAG_TYPE, DELETE_TAG_TYPE,
UPDATE_TAG_TYPE,
} from '../../providers/AccessProvider/permissions'; } from '../../providers/AccessProvider/permissions';
import Dialogue from '../../common/Dialogue/Dialogue'; import Dialogue from '../../common/Dialogue/Dialogue';
import useMediaQuery from '@material-ui/core/useMediaQuery'; import useMediaQuery from '@material-ui/core/useMediaQuery';
import styles from '../TagType.module.scss'; import styles from '../TagType.module.scss';
import AccessContext from '../../../contexts/AccessContext'; import AccessContext from '../../../contexts/AccessContext';
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
import useTagTypes from '../../../hooks/api/getters/useTagTypes/useTagTypes';
import useToast from '../../../hooks/useToast';
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType }) => { const TagTypeList = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const [deletion, setDeletion] = useState({ open: false }); const [deletion, setDeletion] = useState({ open: false });
const history = useHistory(); const history = useHistory();
const smallScreen = useMediaQuery('(max-width:700px)'); const smallScreen = useMediaQuery('(max-width:700px)');
const { deleteTagType } = useTagTypesApi();
const { tagTypes, refetch } = useTagTypes();
const { setToastData, setToastApiError } = useToast();
useEffect(() => { const deleteTag = async () => {
fetchTagTypes(); try {
// eslint-disable-next-line react-hooks/exhaustive-deps await deleteTagType(deletion.name);
}, []); refetch();
setDeletion({ open: false });
setToastData({
type: 'success',
show: true,
text: 'Successfully deleted tag type.',
});
} catch (e) {
setToastApiError(e.toString());
}
};
let header = ( let header = (
<HeaderTitle <HeaderTitle
title="Tag Types" title="Tag Types"
actions={ actions={
<ConditionallyRender <ConditionallyRender
condition={hasAccess(CREATE_TAG_TYPE)} condition={hasAccess(UPDATE_TAG_TYPE)}
show={ show={
<ConditionallyRender <ConditionallyRender
condition={smallScreen} condition={smallScreen}
@ -96,6 +110,7 @@ const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType }) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
); );
return ( return (
<ListItem <ListItem
key={`${tagType.name}`} key={`${tagType.name}`}
@ -105,6 +120,13 @@ const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType }) => {
<Label /> <Label />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={link} secondary={tagType.description} /> <ListItemText primary={link} secondary={tagType.description} />
<PermissionIconButton
permission={UPDATE_TAG_TYPE}
component={Link}
to={`/tag-types/edit/${tagType.name}`}
>
<Edit className={styles.icon} />
</PermissionIconButton>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(DELETE_TAG_TYPE)} condition={hasAccess(DELETE_TAG_TYPE)}
show={deleteButton} show={deleteButton}
@ -124,10 +146,7 @@ const TagTypeList = ({ tagTypes, fetchTagTypes, removeTagType }) => {
<Dialogue <Dialogue
title="Really delete Tag type?" title="Really delete Tag type?"
open={deletion.open} open={deletion.open}
onClick={() => { onClick={deleteTag}
removeTagType(deletion.name);
setDeletion({ open: false });
}}
onClose={() => { onClose={() => {
setDeletion({ open: false }); setDeletion({ open: false });
}} }}

View File

@ -38,44 +38,10 @@ exports[`renders a list with elements correctly 1`] = `
className="MuiList-root MuiList-padding" className="MuiList-root MuiList-padding"
> >
<li <li
className="MuiListItem-root tagListItem MuiListItem-gutters" className="MuiListItem-root MuiListItem-gutters"
disabled={false} disabled={false}
> >
<div No entries
className="MuiListItemIcon-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"
/>
</svg>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline"
>
<span
className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<a
href="/tag-types/edit/simple"
onClick={[Function]}
>
<strong>
simple
</strong>
</a>
</span>
<p
className="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Some simple description
</p>
</div>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -14,11 +14,13 @@ import {
UPDATE_TAG_TYPE, UPDATE_TAG_TYPE,
DELETE_TAG_TYPE, DELETE_TAG_TYPE,
} from '../../providers/AccessProvider/permissions'; } from '../../providers/AccessProvider/permissions';
import UIProvider from '../../providers/UIProvider/UIProvider';
test('renders an empty list correctly', () => { test('renders an empty list correctly', () => {
const tree = renderer.create( const tree = renderer.create(
<MemoryRouter> <MemoryRouter>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<UIProvider>
<AccessProvider <AccessProvider
store={createFakeStore([{ permission: ADMIN }])} store={createFakeStore([{ permission: ADMIN }])}
> >
@ -29,6 +31,7 @@ test('renders an empty list correctly', () => {
history={{}} history={{}}
/> />
</AccessProvider> </AccessProvider>
</UIProvider>
</ThemeProvider> </ThemeProvider>
</MemoryRouter> </MemoryRouter>
); );
@ -39,6 +42,7 @@ test('renders a list with elements correctly', () => {
const tree = renderer.create( const tree = renderer.create(
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<MemoryRouter> <MemoryRouter>
<UIProvider>
<AccessProvider <AccessProvider
store={createFakeStore([ store={createFakeStore([
{ permission: CREATE_TAG_TYPE }, { permission: CREATE_TAG_TYPE },
@ -59,6 +63,7 @@ test('renders a list with elements correctly', () => {
history={{}} history={{}}
/> />
</AccessProvider> </AccessProvider>
</UIProvider>
</MemoryRouter> </MemoryRouter>
</ThemeProvider> </ThemeProvider>
); );

View File

@ -0,0 +1,91 @@
import { useHistory } from 'react-router-dom';
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../hooks/useToast';
import FormTemplate from '../../common/FormTemplate/FormTemplate';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { UPDATE_TAG_TYPE } from '../../providers/AccessProvider/permissions';
import useTagForm from '../hooks/useTagForm';
import TagTypeForm from '../TagTypeForm/TagTypeForm';
const CreateTagType = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const {
tagName,
tagDesc,
setTagName,
setTagDesc,
getTagPayload,
validateNameUniqueness,
errors,
clearErrors,
} = useTagForm();
const { createTag, loading } = useTagTypesApi();
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const validName = await validateNameUniqueness();
if (validName) {
const payload = getTagPayload();
try {
await createTag(payload);
history.push('/tag-types');
setToastData({
title: 'Tag type created',
confetti: true,
type: 'success',
});
} catch (e: any) {
setToastApiError(e.toString());
}
}
};
const formatApiCode = () => {
return `curl --location --request POST '${
uiConfig.unleashUrl
}/api/admin/tag-types' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getTagPayload(), undefined, 2)}'`;
};
const handleCancel = () => {
history.goBack();
};
return (
<FormTemplate
loading={loading}
title="Create tag type"
description="Tag types allow you to group tags together in the management UI"
documentationLink="https://docs.getunleash.io/advanced/tags"
formatApiCode={formatApiCode}
>
<TagTypeForm
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
tagName={tagName}
setTagName={setTagName}
tagDesc={tagDesc}
setTagDesc={setTagDesc}
mode="Create"
clearErrors={clearErrors}
>
<PermissionButton
onClick={handleSubmit}
permission={UPDATE_TAG_TYPE}
type="submit"
>
Create type
</PermissionButton>
</TagTypeForm>
</FormTemplate>
);
};
export default CreateTagType;

View File

@ -0,0 +1,89 @@
import { useHistory, useParams } from 'react-router-dom';
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
import useTagType from '../../../hooks/api/getters/useTagType/useTagType';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../hooks/useToast';
import FormTemplate from '../../common/FormTemplate/FormTemplate';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { UPDATE_TAG_TYPE } from '../../providers/AccessProvider/permissions';
import useTagForm from '../hooks/useTagForm';
import TagForm from '../TagTypeForm/TagTypeForm';
const EditTagType = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const { name } = useParams<{ name: string }>();
const { tagType } = useTagType(name);
const {
tagName,
tagDesc,
setTagName,
setTagDesc,
getTagPayload,
errors,
clearErrors,
} = useTagForm(tagType?.name, tagType?.description);
const { updateTagType, loading } = useTagTypesApi();
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const payload = getTagPayload();
try {
await updateTagType(tagName, payload);
history.push('/tag-types');
setToastData({
title: 'Tag type updated',
type: 'success',
});
} catch (e: any) {
setToastApiError(e.toString());
}
};
const formatApiCode = () => {
return `curl --location --request PUT '${
uiConfig.unleashUrl
}/api/admin/tag-types/${name}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getTagPayload(), undefined, 2)}'`;
};
const handleCancel = () => {
history.goBack();
};
return (
<FormTemplate
loading={loading}
title="Edit tag type"
description="Tag types allow you to group tags together in the management UI"
documentationLink="https://docs.getunleash.io/"
formatApiCode={formatApiCode}
>
<TagForm
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
tagName={tagName}
setTagName={setTagName}
tagDesc={tagDesc}
setTagDesc={setTagDesc}
mode="Edit"
clearErrors={clearErrors}
>
<PermissionButton
onClick={handleSubmit}
permission={UPDATE_TAG_TYPE}
type="submit"
>
Edit type
</PermissionButton>
</TagForm>
</FormTemplate>
);
};
export default EditTagType;

View File

@ -0,0 +1,47 @@
import { makeStyles } from '@material-ui/core/styles';
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: {
marginRight: '1.5rem',
},
inputDescription: {
marginBottom: '0.5rem',
},
formHeader: {
fontWeight: 'normal',
marginTop: '0',
},
header: {
fontWeight: 'normal',
},
permissionErrorContainer: {
position: 'relative',
},
errorMessage: {
//@ts-ignore
fontSize: theme.fontSizes.smallBody,
color: theme.palette.error.main,
position: 'absolute',
top: '-8px',
},
}));

View File

@ -0,0 +1,77 @@
import Input from '../../common/Input/Input';
import { TextField, Button } from '@material-ui/core';
import { useStyles } from './TagTypeForm.styles';
import React from 'react';
import { trim } from '../../common/util';
import { EDIT } from '../../../constants/misc';
interface ITagTypeForm {
tagName: string;
tagDesc: string;
setTagName: React.Dispatch<React.SetStateAction<string>>;
setTagDesc: React.Dispatch<React.SetStateAction<string>>;
handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string };
mode: string;
clearErrors: () => void;
}
const TagTypeForm: React.FC<ITagTypeForm> = ({
children,
handleSubmit,
handleCancel,
tagName,
tagDesc,
setTagName,
setTagDesc,
errors,
mode,
clearErrors,
}) => {
const styles = useStyles();
return (
<form onSubmit={handleSubmit} className={styles.form}>
<h3 className={styles.formHeader}>Tag information</h3>
<div className={styles.container}>
<p className={styles.inputDescription}>
What is your tag name?
</p>
<Input
className={styles.input}
label="Tag name"
value={tagName}
onChange={e => setTagName(trim(e.target.value))}
error={Boolean(errors.name)}
errorText={errors.name}
onFocus={() => clearErrors()}
disabled={mode === EDIT}
/>
<p className={styles.inputDescription}>
What is this role for?
</p>
<TextField
className={styles.input}
label="Tag description"
variant="outlined"
multiline
maxRows={4}
value={tagDesc}
onChange={e => setTagDesc(e.target.value)}
/>
</div>
<div className={styles.buttonContainer}>
<Button onClick={handleCancel} className={styles.cancelButton}>
Cancel
</Button>
{children}
</div>
</form>
);
};
export default TagTypeForm;

View File

@ -0,0 +1,69 @@
import { useEffect, useState } from 'react';
import useTagTypesApi from '../../../hooks/api/actions/useTagTypesApi/useTagTypesApi';
const useTagForm = (initialTagName = '', initialTagDesc = '') => {
const [tagName, setTagName] = useState(initialTagName);
const [tagDesc, setTagDesc] = useState(initialTagDesc);
const [errors, setErrors] = useState({});
const { validateTagName } = useTagTypesApi();
useEffect(() => {
setTagName(initialTagName);
}, [initialTagName]);
useEffect(() => {
setTagDesc(initialTagDesc);
}, [initialTagDesc]);
const getTagPayload = () => {
return {
name: tagName,
description: tagDesc,
};
};
const NAME_EXISTS_ERROR =
'There already exists a tag-type with the name simple';
const validateNameUniqueness = async () => {
if (tagName.length === 0) {
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
return false;
}
if (tagName.length < 2) {
setErrors(prev => ({
...prev,
name: 'Tag name length must be at least 2 characters long',
}));
return false;
}
try {
await validateTagName(tagName);
return true;
} catch (e: any) {
if (e.toString().includes(NAME_EXISTS_ERROR)) {
setErrors(prev => ({
...prev,
name: NAME_EXISTS_ERROR,
}));
return false;
}
}
};
const clearErrors = () => {
setErrors({});
};
return {
tagName,
tagDesc,
setTagName,
setTagDesc,
getTagPayload,
clearErrors,
validateNameUniqueness,
errors,
};
};
export default useTagForm;

View File

@ -50,6 +50,7 @@ const TagList = ({ tags, fetchTags, removeTag }) => {
<Label /> <Label />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={tag.value} secondary={tag.type} /> <ListItemText primary={tag.value} secondary={tag.type} />
<ConditionallyRender <ConditionallyRender
condition={hasAccess(DELETE_TAG)} condition={hasAccess(DELETE_TAG)}
show={<DeleteButton tagType={tag.type} tagValue={tag.value} />} show={<DeleteButton tagType={tag.type} tagValue={tag.value} />}
@ -81,7 +82,7 @@ const TagList = ({ tags, fetchTags, removeTag }) => {
show={ show={
<IconButton <IconButton
aria-label="add tag" aria-label="add tag"
onClick={() => history.push('/tags/create')} onClick={() => history.push('/tag-types/create')}
> >
<Add /> <Add />
</IconButton> </IconButton>
@ -91,7 +92,9 @@ const TagList = ({ tags, fetchTags, removeTag }) => {
<Button <Button
color="primary" color="primary"
startIcon={<Add />} startIcon={<Add />}
onClick={() => history.push('/tags/create')} onClick={() =>
history.push('/tag-types/create')
}
variant="contained" variant="contained"
> >
Add new tag Add new tag

View File

@ -0,0 +1,75 @@
import { ITagPayload } from '../../../../interfaces/tags';
import useAPI from '../useApi/useApi';
const useTagTypesApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
const createTag = async (payload: ITagPayload) => {
const path = `api/admin/tag-types`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify(payload),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const validateTagName = async (name: string) => {
const path = `api/admin/tag-types/validate`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify({ name }),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const updateTagType = async (tagName: string, payload: ITagPayload) => {
const path = `api/admin/tag-types/${tagName}`;
const req = createRequest(path, {
method: 'PUT',
body: JSON.stringify(payload),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const deleteTagType = async (tagName: string) => {
const path = `api/admin/tag-types/${tagName}`;
const req = createRequest(path, { method: 'DELETE' });
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
return {
createTag,
validateTagName,
updateTagType,
deleteTagType,
errors,
loading
};
};
export default useTagTypesApi;

View File

@ -0,0 +1,41 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
import handleErrorResponses from '../httpErrorResponseHandler';
const useTagType = (name: string, options: SWRConfiguration = {}) => {
const fetcher = async () => {
const path = formatApiPath(`api/admin/tag-types/${name}`);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Tag data'))
.then(res => res.json());
};
const FEATURE_CACHE_KEY = `api/admin/tag-types/${name}`;
const { data, error } = useSWR(FEATURE_CACHE_KEY, fetcher, {
...options,
});
const [loading, setLoading] = useState(!error && !data);
const refetch = () => {
mutate(FEATURE_CACHE_KEY);
};
useEffect(() => {
setLoading(!error && !data);
}, [data, error]);
return {
tagType: data?.tagType || { name: '', description: '' },
error,
loading,
refetch,
FEATURE_CACHE_KEY,
};
};
export default useTagType;

View File

@ -8,3 +8,8 @@ export interface ITagType {
description: string; description: string;
icon: string; icon: string;
} }
export interface ITagPayload {
name: string;
description: string;
}

View File

@ -10,7 +10,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"module": "esnext", "module": "esnext",
@ -18,7 +17,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx",
"strict": true
}, },
"include": [ "include": [
"src" "src"