From dea785fb96b2fb9dc12590ca46e71c3eaec45626 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 8 May 2025 10:25:47 +0200 Subject: [PATCH] feat: edit link UI (#9926) --- .../AddLinkDialogue.tsx | 71 ---------- .../FeatureOverviewMetaData.tsx | 15 ++- .../FeatureOverviewMetaData/LinkDialogue.tsx | 123 ++++++++++++++++++ .../useFeatureLinkApi/useFeatureLinkApi.ts | 12 ++ 4 files changed, 147 insertions(+), 74 deletions(-) delete mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/AddLinkDialogue.tsx create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/LinkDialogue.tsx diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/AddLinkDialogue.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/AddLinkDialogue.tsx deleted file mode 100644 index 67f40e423d..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/AddLinkDialogue.tsx +++ /dev/null @@ -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 = ({ - 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 ( - { - 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' - > - - setUrl(e.target.value)} - /> - setTitle(e.target.value)} - /> - - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx index eb58a8cae6..2ab599a068 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx @@ -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 = ({ links, project, feature }) => { const [showAddLinkDialogue, setShowAddLinkDialogue] = useState(false); + const [editLink, setEditLink] = useState(null); const { deleteLink, loading } = useFeatureLinkApi(project, feature); const { setToastData, setToastApiError } = useToast(); const { refetchFeature } = useFeature(project, feature); @@ -132,7 +133,9 @@ const FeatureLinks: FC = ({ links, project, feature }) => { {}} + onEdit={() => { + setEditLink(link); + }} onDelete={async () => { try { await deleteLink(link.id); @@ -212,9 +215,15 @@ const FeatureLinks: FC = ({ links, project, feature }) => { setShowAddLinkDialogue(false)} /> + setEditLink(null)} + /> ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/LinkDialogue.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/LinkDialogue.tsx new file mode 100644 index 0000000000..1784a532a2 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/LinkDialogue.tsx @@ -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 = ({ + 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 ( + + + setUrl(e.target.value)} + /> + setTitle(e.target.value)} + /> + + + ); +}; + +export const AddLinkDialogue: FC> = ( + props, +) => { + return ; +}; + +export const EditLinkDialogue: FC< + Omit +> = (props) => { + return ( + + ); +}; diff --git a/frontend/src/hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi.ts b/frontend/src/hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi.ts index 94e5cfd8c5..7b1150c890 100644 --- a/frontend/src/hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi.ts +++ b/frontend/src/hooks/api/actions/useFeatureLinkApi/useFeatureLinkApi.ts @@ -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,