mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
feat: edit link UI (#9926)
This commit is contained in:
parent
919db76629
commit
dea785fb96
@ -1,71 +0,0 @@
|
|||||||
import { type FC, useState } from 'react';
|
|
||||||
import { Box, styled, TextField } from '@mui/material';
|
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
|
||||||
import { useFeatureLinkApi } from 'hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi';
|
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
|
|
||||||
interface IAddLinkDialogueProps {
|
|
||||||
project: string;
|
|
||||||
featureId: string;
|
|
||||||
showAddLinkDialogue: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledTextField = styled(TextField)(({ theme }) => ({
|
|
||||||
width: '100%',
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
marginBottom: theme.spacing(1),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const AddLinkDialogue: FC<IAddLinkDialogueProps> = ({
|
|
||||||
showAddLinkDialogue,
|
|
||||||
onClose,
|
|
||||||
project,
|
|
||||||
featureId,
|
|
||||||
}) => {
|
|
||||||
const [url, setUrl] = useState('');
|
|
||||||
const [title, setTitle] = useState('');
|
|
||||||
const { addLink, loading } = useFeatureLinkApi(project, featureId);
|
|
||||||
const { refetchFeature } = useFeature(project, featureId);
|
|
||||||
const { setToastData, setToastApiError } = useToast();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialogue
|
|
||||||
open={showAddLinkDialogue}
|
|
||||||
title='Add link'
|
|
||||||
onClose={onClose}
|
|
||||||
disabledPrimaryButton={url.trim() === '' || loading}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await addLink({ url, title: title ?? null });
|
|
||||||
setToastData({ text: 'Link added', type: 'success' });
|
|
||||||
onClose();
|
|
||||||
refetchFeature();
|
|
||||||
setTitle('');
|
|
||||||
setUrl('');
|
|
||||||
} catch (error) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
primaryButtonText='Add'
|
|
||||||
secondaryButtonText='Cancel'
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<StyledTextField
|
|
||||||
label='Link'
|
|
||||||
variant='outlined'
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
<StyledTextField
|
|
||||||
label='Title (optional)'
|
|
||||||
variant='outlined'
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Dialogue>
|
|
||||||
);
|
|
||||||
};
|
|
@ -32,7 +32,7 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import LinkIcon from '@mui/icons-material/Link';
|
import LinkIcon from '@mui/icons-material/Link';
|
||||||
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
|
||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import { AddLinkDialogue } from './AddLinkDialogue';
|
import { EditLinkDialogue, AddLinkDialogue } from './LinkDialogue';
|
||||||
import { useFeatureLinkApi } from 'hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi';
|
import { useFeatureLinkApi } from 'hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
@ -107,6 +107,7 @@ interface FeatureLinksProps {
|
|||||||
|
|
||||||
const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
|
const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
|
||||||
const [showAddLinkDialogue, setShowAddLinkDialogue] = useState(false);
|
const [showAddLinkDialogue, setShowAddLinkDialogue] = useState(false);
|
||||||
|
const [editLink, setEditLink] = useState<FeatureLink | null>(null);
|
||||||
const { deleteLink, loading } = useFeatureLinkApi(project, feature);
|
const { deleteLink, loading } = useFeatureLinkApi(project, feature);
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { refetchFeature } = useFeature(project, feature);
|
const { refetchFeature } = useFeature(project, feature);
|
||||||
@ -132,7 +133,9 @@ const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
|
|||||||
<ExtraActions
|
<ExtraActions
|
||||||
capabilityId='link'
|
capabilityId='link'
|
||||||
feature={feature}
|
feature={feature}
|
||||||
onEdit={() => {}}
|
onEdit={() => {
|
||||||
|
setEditLink(link);
|
||||||
|
}}
|
||||||
onDelete={async () => {
|
onDelete={async () => {
|
||||||
try {
|
try {
|
||||||
await deleteLink(link.id);
|
await deleteLink(link.id);
|
||||||
@ -212,9 +215,15 @@ const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
|
|||||||
<AddLinkDialogue
|
<AddLinkDialogue
|
||||||
project={project}
|
project={project}
|
||||||
featureId={feature}
|
featureId={feature}
|
||||||
showAddLinkDialogue={showAddLinkDialogue}
|
showDialogue={showAddLinkDialogue}
|
||||||
onClose={() => setShowAddLinkDialogue(false)}
|
onClose={() => setShowAddLinkDialogue(false)}
|
||||||
/>
|
/>
|
||||||
|
<EditLinkDialogue
|
||||||
|
project={project}
|
||||||
|
featureId={feature}
|
||||||
|
link={editLink}
|
||||||
|
onClose={() => setEditLink(null)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react';
|
||||||
|
import { Box, styled, TextField } from '@mui/material';
|
||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import { useFeatureLinkApi } from 'hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import type { FeatureLink } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
|
const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface ILinkDialogueProps {
|
||||||
|
project: string;
|
||||||
|
featureId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
mode: 'add' | 'edit';
|
||||||
|
showDialogue: boolean;
|
||||||
|
link: FeatureLink | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinkDialogue: FC<ILinkDialogueProps> = ({
|
||||||
|
showDialogue,
|
||||||
|
onClose,
|
||||||
|
project,
|
||||||
|
featureId,
|
||||||
|
mode,
|
||||||
|
link,
|
||||||
|
}) => {
|
||||||
|
const [url, setUrl] = useState('');
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [id, setId] = useState('');
|
||||||
|
const { addLink, editLink, loading } = useFeatureLinkApi(
|
||||||
|
project,
|
||||||
|
featureId,
|
||||||
|
);
|
||||||
|
const { refetchFeature } = useFeature(project, featureId);
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const isEditMode = mode === 'edit';
|
||||||
|
const dialogueTitle = isEditMode ? 'Edit link' : 'Add link';
|
||||||
|
const successMessage = isEditMode ? 'Link updated' : 'Link added';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEditMode && link) {
|
||||||
|
setUrl(link.url || '');
|
||||||
|
setTitle(link.title || '');
|
||||||
|
setId(link.id || '');
|
||||||
|
} else if (!isEditMode) {
|
||||||
|
setUrl('');
|
||||||
|
setTitle('');
|
||||||
|
setId('');
|
||||||
|
}
|
||||||
|
}, [isEditMode, link]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (isEditMode) {
|
||||||
|
await editLink(id, { url, title: title || null });
|
||||||
|
} else {
|
||||||
|
await addLink({ url, title: title || null });
|
||||||
|
}
|
||||||
|
|
||||||
|
setToastData({ text: successMessage, type: 'success' });
|
||||||
|
onClose();
|
||||||
|
refetchFeature();
|
||||||
|
setTitle('');
|
||||||
|
setUrl('');
|
||||||
|
} catch (error) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOpen = isEditMode ? link !== null : showDialogue;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialogue
|
||||||
|
open={isOpen}
|
||||||
|
title={dialogueTitle}
|
||||||
|
onClose={onClose}
|
||||||
|
disabledPrimaryButton={url.trim() === '' || loading}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
primaryButtonText='Save'
|
||||||
|
secondaryButtonText='Cancel'
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<StyledTextField
|
||||||
|
label='Link'
|
||||||
|
variant='outlined'
|
||||||
|
value={url}
|
||||||
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
<StyledTextField
|
||||||
|
label='Title (optional)'
|
||||||
|
variant='outlined'
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Dialogue>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddLinkDialogue: FC<Omit<ILinkDialogueProps, 'mode' | 'link'>> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
|
return <LinkDialogue {...props} mode='add' link={null} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditLinkDialogue: FC<
|
||||||
|
Omit<ILinkDialogueProps, 'mode' | 'showDialogue'>
|
||||||
|
> = (props) => {
|
||||||
|
return (
|
||||||
|
<LinkDialogue
|
||||||
|
{...props}
|
||||||
|
mode='edit'
|
||||||
|
showDialogue={props.link !== null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -19,6 +19,17 @@ export const useFeatureLinkApi = (project: string, feature: string) => {
|
|||||||
await makeRequest(req.caller, req.id);
|
await makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editLink = async (linkId: string, linkSchema: FeatureLinkSchema) => {
|
||||||
|
const req = createRequest(
|
||||||
|
`/api/admin/projects/${project}/features/${feature}/link/${linkId}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(linkSchema),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
const deleteLink = async (linkId: string) => {
|
const deleteLink = async (linkId: string) => {
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
`/api/admin/projects/${project}/features/${feature}/link/${linkId}`,
|
`/api/admin/projects/${project}/features/${feature}/link/${linkId}`,
|
||||||
@ -37,6 +48,7 @@ export const useFeatureLinkApi = (project: string, feature: string) => {
|
|||||||
];
|
];
|
||||||
return {
|
return {
|
||||||
addLink: useCallback(addLink, callbackDeps),
|
addLink: useCallback(addLink, callbackDeps),
|
||||||
|
editLink: useCallback(editLink, callbackDeps),
|
||||||
deleteLink: useCallback(deleteLink, callbackDeps),
|
deleteLink: useCallback(deleteLink, callbackDeps),
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
|
Loading…
Reference in New Issue
Block a user