mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-27 13:49:10 +02:00
feat: add link ui (#9918)
This commit is contained in:
parent
eb238f502a
commit
36c8efceae
@ -0,0 +1,71 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -30,6 +30,9 @@ import AddIcon from '@mui/icons-material/Add';
|
|||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
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 PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
|
import { AddLinkDialogue } from './AddLinkDialogue';
|
||||||
|
|
||||||
const StyledMetaDataContainer = styled('div')(({ theme }) => ({
|
const StyledMetaDataContainer = styled('div')(({ theme }) => ({
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@ -91,7 +94,13 @@ type FeatureOverviewMetaDataProps = {
|
|||||||
onChange: () => void;
|
onChange: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeatureLinks: FC<{ links: FeatureLink[] }> = ({ links }) => {
|
const FeatureLinks: FC<{
|
||||||
|
links: FeatureLink[];
|
||||||
|
project: string;
|
||||||
|
feature: string;
|
||||||
|
}> = ({ links, project, feature }) => {
|
||||||
|
const [showAddLinkDialogue, setShowAddLinkDialogue] = useState(false);
|
||||||
|
|
||||||
return links.length === 0 ? (
|
return links.length === 0 ? (
|
||||||
<StyledMetaDataContainer>
|
<StyledMetaDataContainer>
|
||||||
<StyledTitle>
|
<StyledTitle>
|
||||||
@ -105,6 +114,18 @@ const FeatureLinks: FC<{ links: FeatureLink[] }> = ({ links }) => {
|
|||||||
trackers, code repositories or analytics tooling
|
trackers, code repositories or analytics tooling
|
||||||
</StyledMetaDataItem>
|
</StyledMetaDataItem>
|
||||||
<div>
|
<div>
|
||||||
|
<PermissionButton
|
||||||
|
size='small'
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={project}
|
||||||
|
variant='text'
|
||||||
|
onClick={() => {
|
||||||
|
setShowAddLinkDialogue(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add parent flag
|
||||||
|
</PermissionButton>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size='small'
|
||||||
variant='text'
|
variant='text'
|
||||||
@ -150,11 +171,17 @@ const FeatureLinks: FC<{ links: FeatureLink[] }> = ({ links }) => {
|
|||||||
size='small'
|
size='small'
|
||||||
variant='text'
|
variant='text'
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
onClick={() => {}}
|
onClick={() => setShowAddLinkDialogue(true)}
|
||||||
>
|
>
|
||||||
Add link
|
Add link
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<AddLinkDialogue
|
||||||
|
project={project}
|
||||||
|
featureId={feature}
|
||||||
|
showAddLinkDialogue={showAddLinkDialogue}
|
||||||
|
onClose={() => setShowAddLinkDialogue(false)}
|
||||||
|
/>
|
||||||
</StyledMetaDataContainer>
|
</StyledMetaDataContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -181,7 +208,11 @@ const FeatureOverviewMetaData: FC<FeatureOverviewMetaDataProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{featureLinksEnabled ? (
|
{featureLinksEnabled ? (
|
||||||
<FeatureLinks links={feature.links || []} />
|
<FeatureLinks
|
||||||
|
links={feature.links || []}
|
||||||
|
project={feature.project}
|
||||||
|
feature={feature.name}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<StyledMetaDataContainer>
|
<StyledMetaDataContainer>
|
||||||
<div>
|
<div>
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import useAPI from '../useApi/useApi';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const useFeatureLinkApi = (project: string, feature: string) => {
|
||||||
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
|
propagateErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const addLink = async (linkSchema: {
|
||||||
|
url: string;
|
||||||
|
title: string | null;
|
||||||
|
}) => {
|
||||||
|
const req = createRequest(
|
||||||
|
`/api/admin/projects/${project}/features/${feature}/link`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(linkSchema),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const callbackDeps = [
|
||||||
|
createRequest,
|
||||||
|
makeRequest,
|
||||||
|
formatUnknownError,
|
||||||
|
project,
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
addLink: useCallback(addLink, callbackDeps),
|
||||||
|
errors,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user