From ee080607981a2114976a195ab3ff6245b44cf2ca Mon Sep 17 00:00:00 2001 From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com> Date: Mon, 1 Dec 2025 20:04:36 +0500 Subject: [PATCH] Improve deactivated objects & fields behaviors. (#16090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes [1918](https://github.com/twentyhq/core-team-issues/issues/1918). - For the first point in the issue, we just show the deactivated entries along with the deactivated text. --- - For the second point, we show a banner and control the enabled/disabled state of save button depending on whether we're allowing the user to create table with the typed name. - For example, we do not want to allow the user to create a table with reserved name, so we disable the save button without showing a banner. - Similarly, we do not want the user to create a table with a name that already exists in the database. In this case, we show a banner and we also disable the save button. - Finally, we do not want to allow the user to create a table where singular and plural name are the same. Therefore, we disable the save button for names like `works`. --- - For the third point, if we add the delete button, it logically means that we allow the user to delete a custom object/field even it has not been deactivated yet, so did that. - Upon deleting the object/field, if we wait for the metadata to refetch before we navigate, this is what we see because the path does not exist any longer after deletion and we're waiting for refetch on the path until we navigate away. https://github.com/user-attachments/assets/dbe0569c-db88-4285-851f-22551b1ca81e - To avoid this page from appearing, I replaced awaiting refetch to not awaiting refetch and redirecting while the refetch happens in the background. - Therefore, when we delete something, there is a slight delay for when it is actually cleared out from the list, but the Not Found view does not appear on the screen. https://github.com/user-attachments/assets/47f49579-ce51-4d6a-b857-72046247bb4b - I tried optimistically removing the object/field from the metadata, but it leads to some issues (crashes the app) and I have not been able to find a solution for it yet. - Therefore, instead of getting stuck at perfection and blocking myself, I stopped getting into the issue further and created this PR by ensuring that the desired functionality works. --- > [!NOTE] > Display deactivated objects/fields by default, add delete actions with confirmation, and unify metadata name computation (auto-suffix reserved keywords) across front/back with conflict checks in object creation. > > - **Frontend (Settings/Data Model)**: > - **Visibility/UX**: Show `Deactivated` labels for objects/fields; filters default to include inactive (`showDeactivated`/`showInactive` true); replace field action dropdown with chevron link. > - **Delete flows**: Add delete buttons for custom objects/fields with confirmation modals and background refetch to avoid Not Found flashes. > - **Creation/Edit validation**: Add name conflict detection banner in `SettingsDataModelObjectAboutForm` and disable Save on conflicts; simplify `metadataLabelSchema` to use computed name; form fields validate on change and sync API names. > - **Shared (twenty-shared/metadata)**: > - Add `computeMetadataNameFromLabel` util (slugify+camelCase) and `RESERVED_METADATA_NAME_KEYWORDS`; auto-append `Custom` to reserved names; export constants/utilities. > - **Backend**: > - Migrate to shared `computeMetadataNameFromLabel`; update validators to use shared reserved keywords with new messages; allow deletion of active custom fields/objects (keep standard guards); adjust services/decorators accordingly. > - **Tests/Stories**: > - Update unit/integration snapshots for new reserved-name messages and behaviors; add missing i18n/router decorators in stories. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5b126155606f6dbc8f7f91e2192cffb7bd2ebd2c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: Félix Malfait Co-authored-by: Félix Malfait --- .../validation-schemas/metadataLabelSchema.ts | 25 ++- ...ettingsObjectFieldActiveActionDropdown.tsx | 90 ----------- .../SettingsObjectFieldItemTableRow.tsx | 153 +++++++----------- .../components/SettingsObjectItemTableRow.tsx | 38 ++++- .../components/tabs/ObjectSettings.tsx | 82 +++++++++- .../SettingsDataModelObjectAboutForm.tsx | 70 +++++++- ...ttingsDataModelObjectAboutForm.stories.tsx | 3 +- .../utils/getConflictingObjectMetadataItem.ts | 37 +++++ ...ttingsApiKeysFieldItemTableRow.stories.tsx | 3 +- .../settings/data-model/SettingsNewObject.tsx | 24 ++- .../data-model/SettingsObjectDetailPage.tsx | 23 ++- .../data-model/SettingsObjectFieldEdit.tsx | 125 +++++++++++--- .../data-model/SettingsObjectFieldTable.tsx | 2 +- .../data-model/SettingsObjectTable.tsx | 2 +- .../SettingsObjectDetail.stories.tsx | 16 +- .../computeMetadataNameFromLabel.test.ts | 16 ++ .../utils/computeMetadataNameFromLabel.ts | 6 +- .../computeMetadataNameFromLabelOrThrow.ts | 22 --- .../ai/ai-agent/agent.service.ts | 2 +- .../field-metadata-morph-relation.service.ts | 2 +- .../field-metadata-relation.service.ts | 2 +- ...-relation-flat-field-metadata-pair.util.ts | 2 +- ...ld-metadata-name-synced-with-label.util.ts | 3 +- ...-metadata-names-synced-with-labels.util.ts | 3 +- ...pute-metadata-name-from-label.util.spec.ts | 30 +++- .../compute-metadata-name-from-label.util.ts | 45 ------ ...name-flat-metadata-validators.constants.ts | 7 +- ...e-metadata-name-is-not-reserved-keyword.ts | 71 +------- ...e-name-and-label-are-sync-or-throw.util.ts | 56 +++---- .../decorators/workspace-field.decorator.ts | 6 +- .../workspace-relation.decorator.ts | 2 +- .../flat-field-metadata-validator.service.ts | 12 -- .../flat-object-metadata-validator.service.ts | 8 - ...bject-metadata-v2.integration-spec.ts.snap | 24 +-- packages/twenty-shared/package.json | 2 + .../compute-metadata-name-from-label.util.ts | 26 +++ packages/twenty-shared/src/metadata/index.ts | 6 + ...eserved-metadata-name-keywords.constant.ts | 69 ++++++++ .../sanitize-reserved-keyword.util.ts | 10 ++ 39 files changed, 644 insertions(+), 481 deletions(-) delete mode 100644 packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/utils/getConflictingObjectMetadataItem.ts delete mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/computeMetadataNameFromLabelOrThrow.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts create mode 100644 packages/twenty-shared/src/metadata/compute-metadata-name-from-label.util.ts create mode 100644 packages/twenty-shared/src/metadata/reserved-metadata-name-keywords.constant.ts create mode 100644 packages/twenty-shared/src/metadata/sanitize-reserved-keyword.util.ts diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts index 1e2a63af9c..622eaeda49 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts @@ -1,8 +1,8 @@ import { errors } from '@/settings/data-model/fields/forms/utils/errorMessages'; import { z } from 'zod'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabelOrThrow'; import { METADATA_LABEL_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataLabelValidPattern'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel'; export const metadataLabelSchema = (existingLabels?: string[]) => { return z .string() @@ -11,12 +11,9 @@ export const metadataLabelSchema = (existingLabels?: string[]) => { .regex(METADATA_LABEL_VALID_PATTERN, errors.LabelNotFormattable) .refine( (label) => { - try { - computeMetadataNameFromLabelOrThrow(label); - return true; - } catch { - return false; - } + const computedName = computeMetadataNameFromLabel(label); + + return computedName !== ''; }, { message: errors.LabelNotFormattable, @@ -24,16 +21,12 @@ export const metadataLabelSchema = (existingLabels?: string[]) => { ) .refine( (label) => { - try { - if (!existingLabels || !label?.length) { - return true; - } - return !existingLabels.includes( - computeMetadataNameFromLabelOrThrow(label), - ); - } catch { - return false; + if (!existingLabels || !label?.length) { + return true; } + const computedName = computeMetadataNameFromLabel(label); + + return computedName !== '' && !existingLabels.includes(computedName); }, { message: errors.LabelNotUnique, diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx deleted file mode 100644 index e1a945b6bd..0000000000 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; -import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; -import { isDefined } from 'twenty-shared/utils'; -import { - IconArchive, - IconDotsVertical, - IconEye, - IconPencil, - IconTextSize, -} from 'twenty-ui/display'; -import { LightIconButton } from 'twenty-ui/input'; -import { MenuItem } from 'twenty-ui/navigation'; - -type SettingsObjectFieldActiveActionDropdownProps = { - isCustomField?: boolean; - onDeactivate?: () => void; - onEdit: () => void; - onSetAsLabelIdentifier?: () => void; - fieldMetadataItemId: string; - readonly?: boolean; -}; - -export const SettingsObjectFieldActiveActionDropdown = ({ - isCustomField, - readonly = false, - onDeactivate, - onEdit, - onSetAsLabelIdentifier, - fieldMetadataItemId, -}: SettingsObjectFieldActiveActionDropdownProps) => { - const dropdownId = `${fieldMetadataItemId}-settings-field-active-action-dropdown`; - - const { closeDropdown } = useCloseDropdown(); - - const handleEdit = () => { - onEdit(); - closeDropdown(dropdownId); - }; - - const handleDeactivate = () => { - onDeactivate?.(); - closeDropdown(dropdownId); - }; - - const handleSetAsLabelIdentifier = () => { - onSetAsLabelIdentifier?.(); - closeDropdown(dropdownId); - }; - - return ( - - } - dropdownComponents={ - - - - {isDefined(onSetAsLabelIdentifier) && !readonly && ( - - )} - {isDefined(onDeactivate) && !readonly && ( - - )} - - - } - /> - ); -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx index 785dcaa27c..fa6a39b3b5 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx @@ -2,36 +2,34 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useDeleteOneFieldMetadataItem } from '@/object-metadata/hooks/useDeleteOneFieldMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; -import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; -import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; -import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown'; import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; import { useMemo } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { FieldMetadataType, SettingsPath } from 'twenty-shared/types'; +import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { - getSettingsPath, - isDefined, - isLabelIdentifierFieldMetadataTypes, -} from 'twenty-shared/utils'; -import { IconMinus, IconPlus, useIcons } from 'twenty-ui/display'; + IconChevronRight, + IconMinus, + IconPlus, + useIcons, +} from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { RelationType } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem'; +import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly'; import { RELATION_TYPES } from '../../constants/RelationTypes'; import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType'; -import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly'; type SettingsObjectFieldItemTableRowProps = { settingsObjectDetailTableItem: SettingsObjectDetailTableItem; @@ -48,22 +46,50 @@ const StyledNameTableCell = styled(TableCell)` gap: ${({ theme }) => theme.spacing(2)}; `; +const StyledNameContainer = styled.div` + display: flex; + align-items: center; + flex: 1; + min-width: 0; + gap: ${({ theme }) => theme.spacing(1)}; +`; + const StyledNameLabel = styled.div` white-space: nowrap; text-overflow: ellipsis; overflow: hidden; `; +const StyledInactiveLabel = styled.span` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.sm}; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + flex: 0 999 auto; + min-width: 48px; + + &::before { + content: '·'; + margin-right: ${({ theme }) => theme.spacing(1)}; + } +`; + const StyledIconTableCell = styled(TableCell)` justify-content: center; padding-right: ${({ theme }) => theme.spacing(1)}; `; +const StyledIconChevronRight = styled(IconChevronRight)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + export const SettingsObjectFieldItemTableRow = ({ settingsObjectDetailTableItem, mode, status, }: SettingsObjectFieldItemTableRowProps) => { + const { t } = useLingui(); const { fieldMetadataItem, identifierType, objectMetadataItem } = settingsObjectDetailTableItem; @@ -80,10 +106,6 @@ export const SettingsObjectFieldItemTableRow = ({ const navigate = useNavigateSettings(); - const [navigationMemorizedUrl, setNavigationMemorizedUrl] = useRecoilState( - navigationMemorizedUrlState, - ); - const theme = useTheme(); const { getIcon } = useIcons(); const Icon = getIcon(fieldMetadataItem.icon); @@ -109,73 +131,15 @@ export const SettingsObjectFieldItemTableRow = ({ const canToggleField = !isLabelIdentifier; - const canBeSetAsLabelIdentifier = - objectMetadataItem.isCustom && - !isLabelIdentifier && - isLabelIdentifierFieldMetadataTypes(fieldMetadataItem.type); - const linkToNavigate = getSettingsPath(SettingsPath.ObjectFieldEdit, { objectNamePlural: objectMetadataItem.namePlural, fieldName: fieldMetadataItem.name, }); - const { activateMetadataField, deactivateMetadataField } = - useFieldMetadataItem(); + const { activateMetadataField } = useFieldMetadataItem(); const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem(); - const handleDisableField = async ( - activeFieldMetadatItem: FieldMetadataItem, - ) => { - if (readonly) { - return; - } - - const deactivationResult = await deactivateMetadataField( - activeFieldMetadatItem.id, - objectMetadataItem.id, - ); - - if (deactivationResult.status === 'failed') { - return; - } - - // TODO: Add optimistic rendering for core views - const deletedViewIds: string[] = []; - - const [baseUrl, queryParams] = navigationMemorizedUrl.includes('?') - ? navigationMemorizedUrl.split('?') - : [navigationMemorizedUrl, '']; - - const params = new URLSearchParams(queryParams); - const currentViewId = params.get('view'); - - if (isDefined(currentViewId) && deletedViewIds.includes(currentViewId)) { - params.delete('view'); - const updatedUrl = params.toString() - ? `${baseUrl}?${params.toString()}` - : baseUrl; - setNavigationMemorizedUrl(updatedUrl); - } - }; - - const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); - - const handleSetLabelIdentifierField = ( - activeFieldMetadatItem: FieldMetadataItem, - ) => { - if (readonly) { - return; - } - - updateOneObjectMetadataItem({ - idToUpdate: objectMetadataItem.id, - updatePayload: { - labelIdentifierFieldMetadataId: activeFieldMetadatItem.id, - }, - }); - }; - const [, setActiveSettingsObjectFields] = useRecoilState( settingsObjectFieldsFamilyState({ objectMetadataItemId: objectMetadataItem.id, @@ -249,9 +213,14 @@ export const SettingsObjectFieldItemTableRow = ({ stroke={theme.icon.stroke.sm} /> )} - - {fieldMetadataItem.label} - + + + {fieldMetadataItem.label} + + {!fieldMetadataItem.isActive && ( + {t`Deactivated`} + )} + @@ -281,27 +250,17 @@ export const SettingsObjectFieldItemTableRow = ({ {status === 'active' ? ( mode === 'view' ? ( - - navigate(SettingsPath.ObjectFieldEdit, { - objectNamePlural: objectMetadataItem.namePlural, - fieldName: fieldMetadataItem.name, - }) - } - onSetAsLabelIdentifier={ - canBeSetAsLabelIdentifier - ? () => handleSetLabelIdentifierField(fieldMetadataItem) - : undefined - } - onDeactivate={ - isLabelIdentifier - ? undefined - : () => handleDisableField(fieldMetadataItem) - } - /> + + + ) : ( canToggleField && ( theme.spacing(2)}; `; +const StyledNameContainer = styled.div` + display: flex; + align-items: center; + flex: 1; + min-width: 0; + gap: ${({ theme }) => theme.spacing(1)}; +`; + const StyledNameLabel = styled.div` white-space: nowrap; text-overflow: ellipsis; overflow: hidden; `; +const StyledInactiveLabel = styled.span` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.sm}; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + flex: 0 999 auto; + min-width: 48px; + + &::before { + content: '·'; + margin-right: ${({ theme }) => theme.spacing(1)}; + } +`; + const StyledActionTableCell = styled(TableCell)` justify-content: center; padding-right: ${({ theme }) => theme.spacing(1)}; @@ -41,6 +65,7 @@ export const SettingsObjectMetadataItemTableRow = ({ link, totalObjectCount, }: SettingsObjectMetadataItemTableRowProps) => { + const { t } = useLingui(); const theme = useTheme(); const { getIcon } = useIcons(); @@ -56,9 +81,14 @@ export const SettingsObjectMetadataItemTableRow = ({ stroke={theme.icon.stroke.sm} /> )} - - {objectMetadataItem.labelPlural} - + + + {objectMetadataItem.labelPlural} + + {!objectMetadataItem.isActive && ( + {t`Deactivated`} + )} + diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx index 44b2338aaa..7a1c42a93f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx @@ -1,21 +1,27 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem'; import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly'; import { SettingsUpdateDataModelObjectAboutForm } from '@/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm'; import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { H2Title, IconArchive } from 'twenty-ui/display'; +import { H2Title, IconArchive, IconTrash } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; type ObjectSettingsProps = { objectMetadataItem: ObjectMetadataItem; + isDeleting: boolean; + setIsDeleting: (isDeleting: boolean) => void; }; const StyledContentContainer = styled.div` @@ -28,7 +34,18 @@ const StyledFormSection = styled(Section)` padding-left: 0 !important; `; -export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { +const StyledDangerButtonsContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const DELETE_OBJECT_MODAL_ID = 'delete-object-confirmation-modal'; + +export const ObjectSettings = ({ + objectMetadataItem, + isDeleting, + setIsDeleting, +}: ObjectSettingsProps) => { const { t } = useLingui(); const currentWorkspace = useRecoilValue(currentWorkspaceState); const readonly = isObjectMetadataSettingsReadOnly({ @@ -38,6 +55,9 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { }); const navigate = useNavigateSettings(); const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); + const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem(); + const { enqueueSuccessSnackBar } = useSnackBar(); + const { openModal, closeModal } = useModal(); const handleDisable = async () => { const result = await updateOneObjectMetadataItem({ @@ -50,6 +70,29 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { } }; + const handleDelete = () => { + openModal(DELETE_OBJECT_MODAL_ID); + }; + + const confirmDelete = async () => { + setIsDeleting(true); + const result = await deleteOneObjectMetadataItem(objectMetadataItem.id); + + if (result.status === 'successful') { + enqueueSuccessSnackBar({ + message: t`Object deleted`, + }); + closeModal(DELETE_OBJECT_MODAL_ID); + navigate(SettingsPath.Objects); + return; + } + + setIsDeleting(false); + closeModal(DELETE_OBJECT_MODAL_ID); + }; + + const objectLabel = objectMetadataItem.labelPlural; + return ( @@ -79,15 +122,38 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { title={t`Danger zone`} description={t`Deactivate object`} /> -