1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: edit link UI (#9926)

This commit is contained in:
Mateusz Kwasniewski 2025-05-08 10:25:47 +02:00 committed by GitHub
parent 919db76629
commit dea785fb96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 147 additions and 74 deletions

View File

@ -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>
);
};

View File

@ -32,7 +32,7 @@ import { Badge } from 'component/common/Badge/Badge';
import LinkIcon from '@mui/icons-material/Link';
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
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 useToast from 'hooks/useToast';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
@ -107,6 +107,7 @@ interface FeatureLinksProps {
const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
const [showAddLinkDialogue, setShowAddLinkDialogue] = useState(false);
const [editLink, setEditLink] = useState<FeatureLink | null>(null);
const { deleteLink, loading } = useFeatureLinkApi(project, feature);
const { setToastData, setToastApiError } = useToast();
const { refetchFeature } = useFeature(project, feature);
@ -132,7 +133,9 @@ const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
<ExtraActions
capabilityId='link'
feature={feature}
onEdit={() => {}}
onEdit={() => {
setEditLink(link);
}}
onDelete={async () => {
try {
await deleteLink(link.id);
@ -212,9 +215,15 @@ const FeatureLinks: FC<FeatureLinksProps> = ({ links, project, feature }) => {
<AddLinkDialogue
project={project}
featureId={feature}
showAddLinkDialogue={showAddLinkDialogue}
showDialogue={showAddLinkDialogue}
onClose={() => setShowAddLinkDialogue(false)}
/>
<EditLinkDialogue
project={project}
featureId={feature}
link={editLink}
onClose={() => setEditLink(null)}
/>
</>
);
};

View File

@ -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}
/>
);
};

View File

@ -19,6 +19,17 @@ export const useFeatureLinkApi = (project: string, feature: string) => {
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 req = createRequest(
`/api/admin/projects/${project}/features/${feature}/link/${linkId}`,
@ -37,6 +48,7 @@ export const useFeatureLinkApi = (project: string, feature: string) => {
];
return {
addLink: useCallback(addLink, callbackDeps),
editLink: useCallback(editLink, callbackDeps),
deleteLink: useCallback(deleteLink, callbackDeps),
errors,
loading,