fix: make add-select-option work on record detail pages (#23420)
Fixes #23339 Follow-up to #23410, which fixed the neighbouring issue (#23341) for users *without* the `DATA_MODEL` permission. ## Problem In #23339 the reporter added a multiselect field to Person and then found the inline "create option" prompt unresponsive. They clearly have `DATA_MODEL` permission, since they just created the field, so the permission check isn't what's blocking them. The blocker is the *other* precondition. Both hooks read the object name from the router: ```ts const { objectNamePlural } = useParams(); ``` `objectNamePlural` only exists on record index routes (`/objects/:objectNamePlural`). Record **detail** pages are `/object/:objectNameSingular/:objectRecordId`, so on a record page the param is `undefined` and: - `useCanAddSelectOption` returns false via `isNonEmptyString(objectNamePlural)` - `useAddSelectOption` bails out at `if (!fieldName || !objectNamePlural) return;` So the action was dead on record pages for **every** user, admins included, and the navigation target it needed was never reachable from there. ## Fix Resolve the field and its object from `fieldMetadataId`, which `FieldDefinition` already carries, instead of reading the object name off the URL: ```ts const { fieldMetadataItem, objectMetadataItem } = useFieldMetadataItemById(fieldMetadataId); ``` This drops the route dependency entirely, so the action behaves the same wherever the field is rendered, and `canAddSelectOption` now reflects only the real permission check. It also lets both hooks take a single `fieldMetadataId` argument: `fieldMetadataItem.name` is the same value the callers were previously passing as `fieldName`, so that parameter is no longer needed. Resolving both values from one id means the guard and the action can't disagree about which field they're describing. `useFieldMetadataItemById` is used rather than `useFieldMetadataItemByIdOrThrow` because a lookup miss should disable the prompt, not crash the field input. ## Reproduction On the code the reporter was running (immediately before #23410), as an **admin** with full `DATA_MODEL`, on a company record page, typing a value matching no option: - `Add "…" to options` renders - clicking it does nothing — URL unchanged, no navigation - pressing <kbd>Enter</kbd> does nothing either which matches #23339 exactly, including the note about the Enter keypress. After #23410 the same root cause shows up differently: the prompt is no longer rendered at all on record pages, since the guard it's now gated on is false there. Still broken, just silent. ## Testing Verified manually on a local instance, swapping only these files between three states and re-running the identical steps on the same cell. | code state | user | route | result | |---|---|---|---| | before #23410 | Admin | `/object/company/:id` | prompt shown, click and Enter both do nothing | | current main | Admin | `/object/company/:id` | prompt not shown, action unreachable | | **this PR** | Admin | `/object/company/:id` | prompt shown, navigates to `/settings/objects/companies/workPolicy?newOption=…` | | **this PR** | Admin | `/objects/tasks` (`Status`, single select) | still works, navigates to `/settings/objects/tasks/status?newOption=…` | | **this PR** | Member (no `DATA_MODEL`) | `/object/company/:id` | prompt not shown | The settings form opens with the typed value prefilled alongside the existing options, so the end-to-end flow works from a record page for the first time. The Member row confirms #23341 stays fixed: dropping the route dependency doesn't weaken the permission gate. The single-select row covers `SelectFieldInput`, which takes the same change. `nx lint:diff-with-main twenty-front` and `nx typecheck twenty-front` both pass.
This commit is contained in:
+7
-3
@@ -1,13 +1,14 @@
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { shouldNavigateBackToMemorizedUrlOnSaveState } from '@/ui/navigation/states/shouldNavigateBackToMemorizedUrlOnSaveState';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
export const useAddSelectOption = (fieldName: string) => {
|
||||
const { objectNamePlural } = useParams();
|
||||
export const useAddSelectOption = (fieldMetadataId: string) => {
|
||||
const { fieldMetadataItem, objectMetadataItem } =
|
||||
useFieldMetadataItemById(fieldMetadataId);
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const setNavigationMemorizedUrl = useSetAtomState(
|
||||
navigationMemorizedUrlState,
|
||||
@@ -17,6 +18,9 @@ export const useAddSelectOption = (fieldName: string) => {
|
||||
shouldNavigateBackToMemorizedUrlOnSaveState,
|
||||
);
|
||||
|
||||
const fieldName = fieldMetadataItem?.name;
|
||||
const objectNamePlural = objectMetadataItem?.namePlural;
|
||||
|
||||
const addSelectOption = useCallback(
|
||||
(optionName: string) => {
|
||||
if (!fieldName || !objectNamePlural) return;
|
||||
|
||||
+6
-5
@@ -1,10 +1,11 @@
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { PermissionFlagType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useCanAddSelectOption = (fieldName: string) => {
|
||||
const { objectNamePlural } = useParams();
|
||||
export const useCanAddSelectOption = (fieldMetadataId: string) => {
|
||||
const { fieldMetadataItem, objectMetadataItem } =
|
||||
useFieldMetadataItemById(fieldMetadataId);
|
||||
|
||||
const userHasPermissionToEditDataModel = useHasPermissionFlag(
|
||||
PermissionFlagType.DATA_MODEL,
|
||||
@@ -12,8 +13,8 @@ export const useCanAddSelectOption = (fieldName: string) => {
|
||||
|
||||
const canAddSelectOption =
|
||||
userHasPermissionToEditDataModel &&
|
||||
isNonEmptyString(fieldName) &&
|
||||
isNonEmptyString(objectNamePlural);
|
||||
isNonEmptyString(fieldMetadataItem?.name) &&
|
||||
isNonEmptyString(objectMetadataItem?.namePlural);
|
||||
|
||||
return { canAddSelectOption };
|
||||
};
|
||||
|
||||
+2
-2
@@ -13,10 +13,10 @@ import { useContext } from 'react';
|
||||
export const MultiSelectFieldInput = () => {
|
||||
const { fieldDefinition, draftValue, setDraftValue } = useMultiSelectField();
|
||||
const { addSelectOption } = useAddSelectOption(
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
);
|
||||
const { canAddSelectOption } = useCanAddSelectOption(
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
const { onSubmit } = useContext(FieldInputEventContext);
|
||||
|
||||
+2
-2
@@ -19,10 +19,10 @@ import { type SelectOption } from 'twenty-ui/input';
|
||||
export const SelectFieldInput = () => {
|
||||
const { fieldDefinition, fieldValue } = useSelectField();
|
||||
const { addSelectOption } = useAddSelectOption(
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
);
|
||||
const { canAddSelectOption } = useCanAddSelectOption(
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
const { onCancel, onSubmit } = useContext(FieldInputEventContext);
|
||||
|
||||
Reference in New Issue
Block a user