fix: Input fields to have expected behaviour in case of empty / only whitespaces string (#6736)
# ISSUE - Closes #6734 - Closes #6633 - Closes #6733 - Closes #6816 # Description - [x] Don't allow Empty (whitespaces) Objects to Create, all the keyboard shortcuts are also handled for this. https://github.com/user-attachments/assets/1c9add4e-f13f-458b-8f76-63bd868413a2 https://github.com/user-attachments/assets/e72b6ee3-74e4-4517-a230-3eb10db80dc7 Note: we do have one other issue with FullName field #6740 Inorder to test use **shift**. - [x] Api Keys Input Name Field -> New and Detail View Name field shouldn't be empty or string with whitespaces, we won't able to have whitespaces in both. **Try Entering just spaces** https://github.com/user-attachments/assets/b521b49f-648c-4585-9d15-8ff4faed3c3a - [x] Similar to above, Empty webhook endpoint url under **/settings/developers/webhooks/new** won't be created. **Try Entering just spaces** - [x] New Functions or Updating Functions will not able to have whitespaces, empty string as Name. **Try Entering just spaces** https://github.com/user-attachments/assets/09fcf394-c6d9-4080-8efd-462b054a22d0 - [x] under **settings/workspace-members** changes will lead and solve that user won't be able to enter Invite by email as just whitespaces + button is now getting disabled when there is no correct email. **Try Entering just spaces** https://github.com/user-attachments/assets/b352edfa-113b-4645-80fd-db6f120ab5db - [x] Text Input Field, will not allow to start entering with whitespaces and won't take just whitespaces as value spaces between words will work. https://github.com/user-attachments/assets/8c1a0812-45be-4ed2-bd3d-bb4f92147976 - [x] Similarly Number works as per above including shortcuts. https://github.com/user-attachments/assets/9f69cc87-5c3c-43ee-93c4-fa887bc0d7ee - [x] Similarly FullName field works as per above including shortcuts https://github.com/user-attachments/assets/7bb006b2-abf7-44cd-a214-7a2fc68df169 - [x] Pasting fullName is been Improved. - Case 1 (Two Words): If there are exactly two words, return them as is. - Case 2 (More than Two Words): If there are more than two words, return the first two words only. - Case 3 (One Word): If there is only one word, return it as the first name, with an empty string as the last name. - WhiteSpaces have been handled. ``` console.log(splitFullName("John Doe")); // ["John", "Doe"] console.log(splitFullName(" ")); // ["", ""] console.log(splitFullName("John")); // ["John", ""] console.log(splitFullName(" John Doe ")); // ["John", "Doe"] console.log(splitFullName("John Michael Andrew Doe")); // ["John", "Michael"] ``` --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -3,8 +3,7 @@ import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleT
|
||||
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
|
||||
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
import { isDoubleTextFieldEmpty } from '@/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
|
||||
@ -28,46 +27,55 @@ export const FullNameFieldInput = ({
|
||||
onTab,
|
||||
onShiftTab,
|
||||
}: FullNameFieldInputProps) => {
|
||||
const { hotkeyScope, draftValue, setDraftValue } = useFullNameField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const { hotkeyScope, draftValue, setDraftValue, persistFullNameField } =
|
||||
useFullNameField();
|
||||
|
||||
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
||||
return {
|
||||
firstName: newDoubleText.firstValue,
|
||||
lastName: newDoubleText.secondValue,
|
||||
firstName: newDoubleText.firstValue.trim(),
|
||||
lastName: newDoubleText.secondValue.trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const getRequiredDraftValueFromDoubleText = (
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
return isDoubleTextFieldEmpty(newDoubleText)
|
||||
? undefined
|
||||
: convertToFullName(newDoubleText);
|
||||
};
|
||||
|
||||
const handleEnter = (newDoubleText: FieldDoubleText) => {
|
||||
onEnter?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onEnter?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleEscape = (newDoubleText: FieldDoubleText) => {
|
||||
onEscape?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onEscape?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onClickOutside?.(() =>
|
||||
persistFullNameField(convertToFullName(newDoubleText)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleTab = (newDoubleText: FieldDoubleText) => {
|
||||
onTab?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newDoubleText: FieldDoubleText) => {
|
||||
onShiftTab?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onShiftTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleChange = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(convertToFullName(newDoubleText));
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
const handlePaste = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(convertToFullName(newDoubleText));
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -16,6 +16,7 @@ import { MenuItemMultiSelectTag } from '@/ui/navigation/menu-item/components/Men
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
const StyledRelationPickerContainer = styled.div`
|
||||
left: -1px;
|
||||
@ -109,7 +110,11 @@ export const MultiSelectFieldInput = ({
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
onChange={(event) =>
|
||||
setSearchFilter(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.currentTarget.value),
|
||||
)
|
||||
}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
@ -60,7 +60,7 @@ export const NumberFieldInput = ({
|
||||
<TextInput
|
||||
placeholder={fieldDefinition.metadata.placeHolder}
|
||||
autoFocus
|
||||
value={draftValue ?? ''}
|
||||
value={draftValue?.toString() ?? ''}
|
||||
onClickOutside={handleClickOutside}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
|
||||
@ -4,6 +4,7 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
import { useTextField } from '../../hooks/useTextField';
|
||||
|
||||
import { turnIntoUndefinedIfWhitespacesOnly } from '~/utils/string/turnIntoUndefinedIfWhitespacesOnly';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
export type TextFieldInputProps = {
|
||||
@ -25,32 +26,31 @@ export const TextFieldInput = ({
|
||||
useTextField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistField(newText));
|
||||
onEnter?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleEscape = (newText: string) => {
|
||||
onEscape?.(() => persistField(newText));
|
||||
onEscape?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newText: string,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(newText));
|
||||
onClickOutside?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleTab = (newText: string) => {
|
||||
onTab?.(() => persistField(newText));
|
||||
onTab?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newText: string) => {
|
||||
onShiftTab?.(() => persistField(newText));
|
||||
onShiftTab?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
setDraftValue(newText);
|
||||
setDraftValue(turnIntoUndefinedIfWhitespacesOnly(newText));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleText';
|
||||
|
||||
export const isDoubleTextFieldEmpty = (doubleText: FieldDoubleText) => {
|
||||
const { firstValue, secondValue } = doubleText;
|
||||
|
||||
const totalLength = firstValue.trim().length + secondValue.trim().length;
|
||||
|
||||
return totalLength > 0 ? false : true;
|
||||
};
|
||||
@ -25,7 +25,7 @@ import {
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
export type FieldTextDraftValue = string;
|
||||
export type FieldNumberDraftValue = string;
|
||||
export type FieldNumberDraftValue = number;
|
||||
export type FieldDateTimeDraftValue = string;
|
||||
export type FieldPhoneDraftValue = string;
|
||||
export type FieldPhonesDraftValue = {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRef } from 'react';
|
||||
import { Fragment, useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconComponent, IconPlus } from 'twenty-ui';
|
||||
@ -158,16 +158,15 @@ export const SingleEntitySelectMenuItems = ({
|
||||
switch (entity.id) {
|
||||
case 'add-new': {
|
||||
return (
|
||||
<>
|
||||
<Fragment key={entity.id}>
|
||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
<CreateNewButton
|
||||
key={entity.id}
|
||||
onClick={onCreate}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add New"
|
||||
hovered={isSelectedAddNewButton}
|
||||
/>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
case 'select-none': {
|
||||
|
||||
Reference in New Issue
Block a user