From 9da973592d2ddade70e3d1bd327a10d7dc136e0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Tue, 4 Mar 2025 17:27:14 +0100
Subject: [PATCH] 460 create restore one and restore many records action
(#10647)
Closes https://github.com/twentyhq/core-team-issues/issues/460
https://github.com/user-attachments/assets/5271e56d-bf67-49cc-a8da-e25c12171e2e
---
.../constants/DefaultActionsConfigV1.ts | 36 ++++-
.../constants/DefaultActionsConfigV2.ts | 32 +++++
.../constants/WorkflowActionsConfig.ts | 30 ++++
.../hooks/useDeleteMultipleRecordsAction.tsx | 2 +-
.../hooks/useDestroyMultipleRecordsAction.tsx | 2 +-
.../hooks/useRestoreMultipleRecordsAction.tsx | 135 ++++++++++++++++++
.../types/MultipleRecordsActionKeys.ts | 1 +
.../hooks/useDeleteSingleRecordAction.tsx | 2 +-
.../hooks/useDestroySingleRecordAction.tsx | 2 +-
.../hooks/useRestoreSingleRecordAction.tsx | 95 ++++++++++++
.../types/SingleRecordActionsKey.ts | 1 +
.../CurrentWorkspaceMemberFavorites.tsx | 2 +-
.../RecordGroupReorderConfirmationModal.tsx | 4 +-
...RecordIndexFiltersToContextStoreEffect.tsx | 8 +-
.../RecordIndexRemoveSortingModal.tsx | 6 +-
.../RecordDetailRelationRecordsListItem.tsx | 2 +-
.../SettingsAccountsRowDropdownMenu.tsx | 4 +-
.../profile/components/DeleteAccount.tsx | 2 +-
.../profile/components/DeleteWorkspace.tsx | 2 +-
.../RoleAssignmentConfirmationModal.tsx | 2 +-
.../SettingsServerlessFunctionSettingsTab.tsx | 2 +-
.../modal/components/ConfirmationModal.tsx | 6 +-
.../__stories__/ConfirmationModal.stories.tsx | 2 +-
...OverrideWorkflowDraftConfirmationModal.tsx | 2 +-
.../src/pages/settings/SettingsBilling.tsx | 2 +-
.../settings/SettingsWorkspaceMembers.tsx | 2 +-
.../SettingsDevelopersApiKeyDetail.tsx | 4 +-
.../SettingsDevelopersWebhookDetail.tsx | 2 +-
28 files changed, 360 insertions(+), 32 deletions(-)
create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction.tsx
create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction.tsx
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts
index b052bdacc..2580fa5f1 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts
@@ -1,5 +1,6 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
+import { useRestoreMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction';
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
import { useImportRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useImportRecordsNoSelectionRecordAction';
import { useSeeDeletedRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useSeeDeletedRecordsNoSelectionRecordAction';
@@ -7,6 +8,7 @@ import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-action
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
+import { useRestoreSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
@@ -21,6 +23,7 @@ import {
IconFileImport,
IconHeart,
IconHeartOff,
+ IconRefresh,
IconRotate2,
IconTrash,
} from 'twenty-ui';
@@ -127,13 +130,42 @@ export const DEFAULT_ACTIONS_CONFIG_V1: Record<
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useExportMultipleRecordsAction,
},
+ restoreSingleRecord: {
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ key: SingleRecordActionKeys.RESTORE,
+ label: msg`Restore record`,
+ shortLabel: msg`Restore`,
+ position: 7,
+ Icon: IconRefresh,
+ accent: 'default',
+ isPinned: true,
+ availableOn: [
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ],
+ useAction: useRestoreSingleRecordAction,
+ },
+ restoreMultipleRecords: {
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ key: MultipleRecordsActionKeys.RESTORE,
+ label: msg`Restore records`,
+ shortLabel: msg`Restore`,
+ position: 8,
+ Icon: IconRefresh,
+ accent: 'default',
+ isPinned: true,
+ availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
+ useAction: useRestoreMultipleRecordsAction,
+ },
seeDeletedRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.SEE_DELETED_RECORDS,
label: msg`See deleted records`,
shortLabel: msg`Deleted records`,
- position: 7,
+ position: 9,
Icon: IconRotate2,
accent: 'default',
isPinned: false,
@@ -146,7 +178,7 @@ export const DEFAULT_ACTIONS_CONFIG_V1: Record<
key: NoSelectionRecordActionKeys.IMPORT_RECORDS,
label: msg`Import records`,
shortLabel: msg`Import`,
- position: 8,
+ position: 10,
Icon: IconFileImport,
accent: 'default',
isPinned: false,
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts
index dbf340d39..1ff94fd43 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts
@@ -1,6 +1,7 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useDestroyMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction';
import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
+import { useRestoreMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction';
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
import { useCreateNewTableRecordNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction';
import { useImportRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useImportRecordsNoSelectionRecordAction';
@@ -13,6 +14,7 @@ import { useExportNoteAction } from '@/action-menu/actions/record-actions/single
import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction';
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
+import { useRestoreSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
@@ -31,6 +33,7 @@ import {
IconHeart,
IconHeartOff,
IconPlus,
+ IconRefresh,
IconRotate2,
IconTrash,
IconTrashX,
@@ -244,4 +247,33 @@ export const DEFAULT_ACTIONS_CONFIG_V2: Record<
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
useAction: useDestroyMultipleRecordsAction,
},
+ restoreSingleRecord: {
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ key: SingleRecordActionKeys.RESTORE,
+ label: msg`Restore record`,
+ shortLabel: msg`Restore`,
+ position: 15,
+ Icon: IconRefresh,
+ accent: 'default',
+ isPinned: true,
+ availableOn: [
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ],
+ useAction: useRestoreSingleRecordAction,
+ },
+ restoreMultipleRecords: {
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ key: MultipleRecordsActionKeys.RESTORE,
+ label: msg`Restore records`,
+ shortLabel: msg`Restore`,
+ position: 16,
+ Icon: IconRefresh,
+ accent: 'default',
+ isPinned: true,
+ availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
+ useAction: useRestoreMultipleRecordsAction,
+ },
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts
index 216b883e7..b25010e62 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts
@@ -347,4 +347,34 @@ export const WORKFLOW_ACTIONS_CONFIG: Record<
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useImportRecordsNoSelectionRecordAction,
},
+ // TODO: uncomment when restore is implemented for workflows
+ // restoreSingleRecord: {
+ // type: ActionMenuEntryType.Standard,
+ // scope: ActionMenuEntryScope.RecordSelection,
+ // key: SingleRecordActionKeys.RESTORE,
+ // label: msg`Restore workflow`,
+ // shortLabel: msg`Restore`,
+ // position: 15,
+ // Icon: IconRefresh,
+ // accent: 'default',
+ // isPinned: true,
+ // availableOn: [
+ // ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ // ActionViewType.SHOW_PAGE,
+ // ],
+ // useAction: useRestoreSingleRecordAction,
+ // },
+ // restoreMultipleRecords: {
+ // type: ActionMenuEntryType.Standard,
+ // scope: ActionMenuEntryScope.RecordSelection,
+ // key: MultipleRecordsActionKeys.RESTORE,
+ // label: msg`Restore workflows`,
+ // shortLabel: msg`Restore`,
+ // position: 16,
+ // Icon: IconRefresh,
+ // accent: 'default',
+ // isPinned: true,
+ // availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
+ // useAction: useRestoreMultipleRecordsAction,
+ // },
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
index dcd60c2f3..9795021e5 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
@@ -115,7 +115,7 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem =
title={'Delete Records'}
subtitle={`Are you sure you want to delete these records? They can be recovered from the Options menu.`}
onConfirmClick={handleDeleteClick}
- deleteButtonText={'Delete Records'}
+ confirmButtonText={'Delete Records'}
/>
);
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx
index 060c07845..5647fc06b 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx
@@ -126,7 +126,7 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem =
"Are you sure you want to destroy these records? They won't be recoverable anymore."
}
onConfirmClick={handleDestroyClick}
- deleteButtonText={'Destroy Records'}
+ confirmButtonText={'Destroy Records'}
/>
);
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction.tsx
new file mode 100644
index 000000000..5f387a0a8
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction.tsx
@@ -0,0 +1,135 @@
+import { useCallback, useState } from 'react';
+import { isDefined } from 'twenty-shared';
+
+import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
+import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
+import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
+import { BACKEND_BATCH_REQUEST_MAX_COUNT } from '@/object-record/constants/BackendBatchRequestMaxCount';
+import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
+import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
+import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
+import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
+import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
+import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
+import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
+import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
+import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
+import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+
+export const useRestoreMultipleRecordsAction: ActionHookWithObjectMetadataItem =
+ ({ objectMetadataItem }) => {
+ const [isRestoreRecordsModalOpen, setIsRestoreRecordsModalOpen] =
+ useState(false);
+
+ const contextStoreCurrentViewId = useRecoilComponentValueV2(
+ contextStoreCurrentViewIdComponentState,
+ );
+
+ if (!contextStoreCurrentViewId) {
+ throw new Error('Current view ID is not defined');
+ }
+
+ const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
+
+ const { resetTableRowSelection } = useRecordTable({
+ recordTableId: getRecordIndexIdFromObjectNamePluralAndViewId(
+ objectMetadataItem.namePlural,
+ contextStoreCurrentViewId,
+ ),
+ });
+
+ const { restoreManyRecords } = useRestoreManyRecords({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ });
+
+ const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
+ contextStoreNumberOfSelectedRecordsComponentState,
+ );
+
+ const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ );
+
+ const contextStoreFilters = useRecoilComponentValueV2(
+ contextStoreFiltersComponentState,
+ );
+
+ const { filterValueDependencies } = useFilterValueDependencies();
+
+ const deletedAtFilter: RecordGqlOperationFilter = {
+ deletedAt: { is: 'NOT_NULL' },
+ };
+
+ const graphqlFilter = {
+ ...computeContextStoreFilters(
+ contextStoreTargetedRecordsRule,
+ contextStoreFilters,
+ objectMetadataItem,
+ filterValueDependencies,
+ ),
+ ...deletedAtFilter,
+ };
+
+ const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
+
+ const isDeletedFilterActive = contextStoreFilters.some(
+ checkIsSoftDeleteFilter,
+ );
+
+ const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ filter: graphqlFilter,
+ limit: DEFAULT_QUERY_PAGE_SIZE,
+ recordGqlFields: { id: true },
+ });
+
+ const handleRestoreClick = useCallback(async () => {
+ const recordsToRestore = await fetchAllRecordIds();
+ const recordIdsToRestore = recordsToRestore.map((record) => record.id);
+
+ resetTableRowSelection();
+
+ await restoreManyRecords({
+ idsToRestore: recordIdsToRestore,
+ });
+ }, [restoreManyRecords, fetchAllRecordIds, resetTableRowSelection]);
+
+ const isRemoteObject = objectMetadataItem.isRemote;
+
+ const shouldBeRegistered =
+ !hasObjectReadOnlyPermission &&
+ !isRemoteObject &&
+ isDeletedFilterActive &&
+ isDefined(contextStoreNumberOfSelectedRecords) &&
+ contextStoreNumberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT &&
+ contextStoreNumberOfSelectedRecords > 0;
+
+ const onClick = () => {
+ if (!shouldBeRegistered) {
+ return;
+ }
+
+ setIsRestoreRecordsModalOpen(true);
+ };
+
+ const confirmationModal = (
+
+ );
+
+ return {
+ shouldBeRegistered,
+ onClick,
+ ConfirmationModal: confirmationModal,
+ };
+ };
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys.ts
index 2c52d9eab..bc9c148d1 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys.ts
@@ -2,4 +2,5 @@ export enum MultipleRecordsActionKeys {
DELETE = 'delete-multiple-records',
EXPORT = 'export-multiple-records',
DESTROY = 'destroy-multiple-records',
+ RESTORE = 'restore-multiple-records',
}
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
index ced7c172d..e37be1e1e 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
@@ -93,7 +93,7 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
closeRightDrawer({ emitCloseEvent: false });
}
}}
- deleteButtonText={'Delete Record'}
+ confirmButtonText={'Delete Record'}
/>
),
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx
index 0cd03ef53..ca017aef1 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx
@@ -86,7 +86,7 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
closeRightDrawer();
}
}}
- deleteButtonText={'Permanently Destroy Record'}
+ confirmButtonText={'Permanently Destroy Record'}
/>
),
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction.tsx
new file mode 100644
index 000000000..1462ba37d
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction.tsx
@@ -0,0 +1,95 @@
+import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
+import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
+import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
+import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
+import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
+import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
+import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
+import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
+import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
+import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useCallback, useContext, useState } from 'react';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared';
+
+export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
+ objectMetadataItem,
+}) => {
+ const recordId = useSelectedRecordIdOrThrow();
+
+ const [isRestoreRecordModalOpen, setIsRestoreRecordModalOpen] =
+ useState(false);
+
+ const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
+
+ const { resetTableRowSelection } = useRecordTable({
+ recordTableId: objectMetadataItem.namePlural,
+ });
+
+ const { restoreManyRecords } = useRestoreManyRecords({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ });
+
+ const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
+
+ const { closeRightDrawer } = useRightDrawer();
+
+ const handleRestoreClick = useCallback(async () => {
+ resetTableRowSelection();
+
+ await restoreManyRecords({
+ idsToRestore: [recordId],
+ });
+ }, [restoreManyRecords, resetTableRowSelection, recordId]);
+
+ const isRemoteObject = objectMetadataItem.isRemote;
+
+ const isSoftDeleteFilterActive = useRecoilComponentValueV2(
+ isSoftDeleteFilterActiveComponentState,
+ );
+
+ const isShowPage =
+ useRecoilComponentValueV2(contextStoreCurrentViewTypeComponentState) ===
+ ContextStoreViewType.ShowPage;
+
+ const { isInRightDrawer } = useContext(ActionMenuContext);
+
+ const shouldBeRegistered =
+ !isRemoteObject &&
+ isDefined(selectedRecord?.deletedAt) &&
+ !hasObjectReadOnlyPermission &&
+ (isShowPage || isSoftDeleteFilterActive);
+
+ const onClick = () => {
+ if (!shouldBeRegistered) {
+ return;
+ }
+
+ setIsRestoreRecordModalOpen(true);
+ };
+
+ const handleConfirmClick = () => {
+ handleRestoreClick();
+ if (isInRightDrawer) {
+ closeRightDrawer({ emitCloseEvent: false });
+ }
+ };
+
+ return {
+ shouldBeRegistered,
+ onClick,
+ ConfirmationModal: (
+
+ ),
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts
index bca5bb107..019962423 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts
@@ -7,4 +7,5 @@ export enum SingleRecordActionKeys {
NAVIGATE_TO_PREVIOUS_RECORD = 'navigate-to-previous-record-single-record',
EXPORT_NOTE_TO_PDF = 'export-note-to-pdf-single-record',
EXPORT = 'export-single-record',
+ RESTORE = 'restore-single-record',
}
diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
index 25daccb38..c432de023 100644
--- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
@@ -218,7 +218,7 @@ export const CurrentWorkspaceMemberFavorites = ({
title={`Remove ${folder.favorites.length} ${folder.favorites.length > 1 ? 'favorites' : 'favorite'}?`}
subtitle={`This action will delete this favorite folder ${folder.favorites.length > 1 ? `and all ${folder.favorites.length} favorites` : 'and the favorite'} inside. Do you want to continue?`}
onConfirmClick={handleConfirmDelete}
- deleteButtonText="Delete Folder"
+ confirmButtonText="Delete Folder"
/>,
document.body,
)}
diff --git a/packages/twenty-front/src/modules/object-record/record-group/components/RecordGroupReorderConfirmationModal.tsx b/packages/twenty-front/src/modules/object-record/record-group/components/RecordGroupReorderConfirmationModal.tsx
index 0a906ec1a..ab3a26520 100644
--- a/packages/twenty-front/src/modules/object-record/record-group/components/RecordGroupReorderConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-group/components/RecordGroupReorderConfirmationModal.tsx
@@ -30,9 +30,9 @@ export const RecordGroupReorderConfirmationModal = ({
isOpen={isRecordGroupReorderConfirmationModalVisible}
setIsOpen={setIsRecordGroupReorderConfirmationModalVisible}
title="Group sorting"
- subtitle={`Would you like to remove ${recordGroupSort} group sorting ?`}
+ subtitle={`Would you like to remove ${recordGroupSort} group sorting?`}
onConfirmClick={onConfirmClick}
- deleteButtonText="Remove"
+ confirmButtonText="Remove"
/>,
document.body,
);
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect.tsx
index d009c9c61..325cf8935 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect.tsx
@@ -2,19 +2,21 @@ import { useEffect } from 'react';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
-import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
import { unselectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { useRecoilValue } from 'recoil';
export const RecordIndexFiltersToContextStoreEffect = () => {
const { recordIndexId } = useRecordIndexContextOrThrow();
- const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
+ const recordIndexFilters = useRecoilComponentValueV2(
+ currentRecordFiltersComponentState,
+ recordIndexId,
+ );
const setContextStoreTargetedRecords = useSetRecoilComponentStateV2(
contextStoreTargetedRecordsRuleComponentState,
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
index bf56d4962..da92e0b14 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
@@ -31,9 +31,9 @@ export const RecordIndexRemoveSortingModal = () => {
isOpen={isRemoveSortingModalOpen}
setIsOpen={setIsRemoveSortingModalOpen}
title={'Remove sorting?'}
- subtitle={<>This is required to enable manual row reordering.>}
- onConfirmClick={() => handleRemoveClick()}
- deleteButtonText={'Remove Sorting'}
+ subtitle={'This is required to enable manual row reordering.'}
+ onConfirmClick={handleRemoveClick}
+ confirmButtonText={'Remove Sorting'}
/>
>
);
diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx
index 3c03d53ee..407e0e0d6 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx
@@ -300,7 +300,7 @@ export const RecordDetailRelationRecordsListItem = ({
>
}
onConfirmClick={handleConfirmDelete}
- deleteButtonText={`Delete ${relationObjectTypeName}`}
+ confirmButtonText={`Delete ${relationObjectTypeName}`}
/>,
document.body,
)}
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx
index abb8b5f99..bef28aa02 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx
@@ -18,8 +18,8 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
-import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { Trans, useLingui } from '@lingui/react/macro';
+import { useNavigateSettings } from '~/hooks/useNavigateSettings';
type SettingsAccountsRowDropdownMenuProps = {
account: ConnectedAccount;
@@ -106,7 +106,7 @@ export const SettingsAccountsRowDropdownMenu = ({
}
onConfirmClick={deleteAccount}
- deleteButtonText={t`Delete account`}
+ confirmButtonText={t`Delete account`}
/>
>
);
diff --git a/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx b/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx
index 8973c2a26..b5b14342c 100644
--- a/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx
+++ b/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx
@@ -50,7 +50,7 @@ export const DeleteAccount = () => {
>
}
onConfirmClick={deleteAccount}
- deleteButtonText={t`Delete account`}
+ confirmButtonText={t`Delete account`}
/>
>
);
diff --git a/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx b/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx
index 363600596..9e2833ae3 100644
--- a/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx
+++ b/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx
@@ -51,7 +51,7 @@ export const DeleteWorkspace = () => {
}
onConfirmClick={deleteWorkspace}
- deleteButtonText={t`Delete workspace`}
+ confirmButtonText={t`Delete workspace`}
/>
>
);
diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/RoleAssignmentConfirmationModal.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/RoleAssignmentConfirmationModal.tsx
index 4f53eac02..83c3a1435 100644
--- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/RoleAssignmentConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/RoleAssignmentConfirmationModal.tsx
@@ -34,7 +34,7 @@ export const RoleAssignmentConfirmationModal = ({
/>
}
onConfirmClick={onConfirm}
- deleteButtonText={t`Confirm`}
+ confirmButtonText={t`Confirm`}
confirmButtonAccent="blue"
/>
);
diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx
index d9fd86500..2c633d600 100644
--- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx
+++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx
@@ -85,7 +85,7 @@ export const SettingsServerlessFunctionSettingsTab = ({
>
}
onConfirmClick={deleteFunction}
- deleteButtonText="Delete function"
+ confirmButtonText="Delete function"
/>
>
);
diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
index 0ef913ea4..7c639f4b7 100644
--- a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
@@ -24,7 +24,7 @@ export type ConfirmationModalProps = {
subtitle: ReactNode;
setIsOpen: (val: boolean) => void;
onConfirmClick: () => void;
- deleteButtonText?: string;
+ confirmButtonText?: string;
confirmationPlaceholder?: string;
confirmationValue?: string;
confirmButtonAccent?: ButtonAccent;
@@ -70,7 +70,7 @@ export const ConfirmationModal = ({
subtitle,
setIsOpen,
onConfirmClick,
- deleteButtonText = `Delete`,
+ confirmButtonText = 'Confirm',
confirmationValue,
confirmationPlaceholder,
confirmButtonAccent = 'danger',
@@ -159,7 +159,7 @@ export const ConfirmationModal = ({
onClick={handleConfirmClick}
variant="secondary"
accent={confirmButtonAccent}
- title={deleteButtonText}
+ title={confirmButtonText}
disabled={!isValidValue || loading}
fullWidth
dataTestId="confirmation-modal-confirm-button"
diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx
index 004a9f19c..8eb6ac425 100644
--- a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx
@@ -18,7 +18,7 @@ export const Default: Story = {
isOpen: true,
title: 'Pariatur labore.',
subtitle: 'Velit dolore aliquip laborum occaecat fugiat.',
- deleteButtonText: 'Delete',
+ confirmButtonText: 'Delete',
},
decorators: [ComponentDecorator],
};
diff --git a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
index ac9c828b5..c976ec217 100644
--- a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
@@ -47,7 +47,7 @@ export const OverrideWorkflowDraftConfirmationModal = ({
title="A draft already exists"
subtitle="A draft already exists for this workflow. Are you sure you want to erase it?"
onConfirmClick={handleOverrideDraft}
- deleteButtonText={'Override Draft'}
+ confirmButtonText={'Override Draft'}
AdditionalButtons={
{
` ${impact}`
}
onConfirmClick={switchInterval}
- deleteButtonText={t`Change ${to}`}
+ confirmButtonText={t`Change ${to}`}
confirmButtonAccent={'blue'}
/>
diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx
index 9c9c95ca4..9a6641b1b 100644
--- a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx
+++ b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx
@@ -329,7 +329,7 @@ export const SettingsWorkspaceMembers = () => {
workspaceMemberToDelete &&
handleRemoveWorkspaceMember(workspaceMemberToDelete)
}
- deleteButtonText={t`Delete account`}
+ confirmButtonText={t`Delete account`}
/>
);
diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
index 5c4d92284..5f77fb5fb 100644
--- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
@@ -260,7 +260,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
onConfirmClick={deleteIntegration}
- deleteButtonText={t`Delete`}
+ confirmButtonText={t`Delete`}
loading={isLoading}
/>
{
}
onConfirmClick={regenerateApiKey}
- deleteButtonText={t`Regenerate key`}
+ confirmButtonText={t`Regenerate key`}
loading={isLoading}
/>
>
diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx
index ab3950bd2..52b21e17f 100644
--- a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx
@@ -266,7 +266,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
}
onConfirmClick={deleteWebhook}
- deleteButtonText={t`Delete webhook`}
+ confirmButtonText={t`Delete webhook`}
/>