diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx index e7f40c461..c8d7a5f52 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx @@ -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 ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx index e95eeb5e2..7ebe1951e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx @@ -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 = ({ setSearchFilter(event.currentTarget.value)} + onChange={(event) => + setSearchFilter( + turnIntoEmptyStringIfWhitespacesOnly(event.currentTarget.value), + ) + } autoFocus /> diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/NumberFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/NumberFieldInput.tsx index 336aefc99..d5fead466 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/NumberFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/NumberFieldInput.tsx @@ -60,7 +60,7 @@ export const NumberFieldInput = ({ { - 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 ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty.ts new file mode 100644 index 000000000..1ffdc0e78 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty.ts @@ -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; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts index 2e50c1b68..9ab99492f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts @@ -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 = { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx index 6404d5e11..a7f029e12 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx @@ -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 ( - <> + {entitiesToSelect.length > 0 && } - + ); } case 'select-none': { diff --git a/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx b/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx index 3a921e92f..31b5f31b3 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx @@ -1,10 +1,11 @@ -import { useCallback, useEffect, useState } from 'react'; import styled from '@emotion/styled'; +import { useCallback, useEffect, useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useDebouncedCallback } from 'use-debounce'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { TextInput } from '@/ui/input/components/TextInput'; +import isEmpty from 'lodash.isempty'; import { useUpdateWorkspaceMutation } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -40,6 +41,7 @@ export const NameField = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedUpdate = useCallback( useDebouncedCallback(async (name: string) => { + if (isEmpty(name)) return; // update local recoil state when workspace name is updated setCurrentWorkspace((currentValue) => { if (currentValue === null) { diff --git a/packages/twenty-front/src/modules/ui/field/input/components/DoubleTextInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/DoubleTextInput.tsx index 027371815..c003bde59 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/DoubleTextInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/DoubleTextInput.tsx @@ -13,6 +13,8 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { isDefined } from '~/utils/isDefined'; +import { splitFullName } from '~/utils/format/spiltFullName'; +import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly'; import { StyledTextInput } from './TextInput'; const StyledContainer = styled.div` @@ -167,9 +169,12 @@ export const DoubleTextInput = ({ const name = event.clipboardData.getData('Text'); - const splittedName = name.split(' '); + const splittedName = splitFullName(name); - onPaste?.({ firstValue: splittedName[0], secondValue: splittedName[1] }); + onPaste?.({ + firstValue: splittedName[0], + secondValue: splittedName[1], + }); }; const handleClickToPreventParentClickEvents = ( @@ -189,7 +194,10 @@ export const DoubleTextInput = ({ placeholder={firstValuePlaceholder} value={firstInternalValue} onChange={(event: ChangeEvent) => { - handleChange(event.target.value, secondInternalValue); + handleChange( + turnIntoEmptyStringIfWhitespacesOnly(event.target.value), + secondInternalValue, + ); }} onPaste={(event: ClipboardEvent) => handleOnPaste(event) @@ -203,7 +211,10 @@ export const DoubleTextInput = ({ placeholder={secondValuePlaceholder} value={secondInternalValue} onChange={(event: ChangeEvent) => { - handleChange(firstInternalValue, event.target.value); + handleChange( + firstInternalValue, + turnIntoEmptyStringIfWhitespacesOnly(event.target.value), + ); }} onClick={handleClickToPreventParentClickEvents} /> diff --git a/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx index b9a08ef1f..c2da43576 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx @@ -1,11 +1,12 @@ +import styled from '@emotion/styled'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; -import styled from '@emotion/styled'; import { TEXT_INPUT_STYLE } from 'twenty-ui'; import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton'; import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; import { isDefined } from '~/utils/isDefined'; +import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly'; export type TextAreaInputProps = { disabled?: boolean; @@ -67,10 +68,12 @@ export const TextAreaInput = ({ copyButton = true, }: TextAreaInputProps) => { const [internalText, setInternalText] = useState(value); - const handleChange = (event: ChangeEvent) => { - setInternalText(event.target.value); - onChange?.(event.target.value); + const targetValue = turnIntoEmptyStringIfWhitespacesOnly( + event.target.value, + ); + setInternalText(targetValue); + onChange?.(targetValue); }; const wrapperRef = useRef(null); diff --git a/packages/twenty-front/src/modules/ui/field/input/components/TextInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/TextInput.tsx index 1490e1f7d..b932cbdc5 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/TextInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/TextInput.tsx @@ -1,5 +1,5 @@ -import { ChangeEvent, useEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { TEXT_INPUT_STYLE } from 'twenty-ui'; import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton'; @@ -44,12 +44,11 @@ export const TextInput = ({ const copyRef = useRef(null); const handleChange = (event: ChangeEvent) => { - setInternalText(event.target.value); - onChange?.(event.target.value); + setInternalText(event.target.value.trim()); + onChange?.(event.target.value.trim()); }; - useEffect(() => { - setInternalText(value); + setInternalText(value.trim()); }, [value]); useRegisterInputEvents({ diff --git a/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx b/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx index aee5943e6..9b50504c7 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx @@ -1,9 +1,10 @@ +import styled from '@emotion/styled'; import { FocusEventHandler } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; -import styled from '@emotion/styled'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly'; import { InputHotkeyScope } from '../types/InputHotkeyScope'; const MAX_ROWS = 5; @@ -75,7 +76,9 @@ export const TextArea = ({ maxRows={MAX_ROWS} minRows={computedMinRows} value={value} - onChange={(event) => onChange?.(event.target.value)} + onChange={(event) => + onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value)) + } onFocus={handleFocus} onBlur={handleBlur} disabled={disabled} diff --git a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx index f7a5ddf8b..cf4b06f6a 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx @@ -11,6 +11,7 @@ import { } from 'react'; import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui'; import { useCombinedRefs } from '~/hooks/useCombinedRefs'; +import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly'; const StyledContainer = styled.div< Pick @@ -180,7 +181,7 @@ const TextInputV2Component = ( )} ) => { - onChange?.(event.target.value); + onChange?.( + turnIntoEmptyStringIfWhitespacesOnly(event.target.value), + ); }} onKeyDown={onKeyDown} {...{ diff --git a/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx index f352d7343..ed9586bf9 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx @@ -30,7 +30,6 @@ const StyledTopBarContainer = styled.div<{ width?: number }>` padding: ${({ theme }) => theme.spacing(2)}; padding-left: 0; padding-right: ${({ theme }) => theme.spacing(3)}; - z-index: 20; width: ${({ width }) => width + 'px' || '100%'}; @media (max-width: ${MOBILE_VIEWPORT}px) { diff --git a/packages/twenty-front/src/modules/workspace/components/WorkspaceInviteTeam.tsx b/packages/twenty-front/src/modules/workspace/components/WorkspaceInviteTeam.tsx index 1f5b277ab..4c007b518 100644 --- a/packages/twenty-front/src/modules/workspace/components/WorkspaceInviteTeam.tsx +++ b/packages/twenty-front/src/modules/workspace/components/WorkspaceInviteTeam.tsx @@ -72,13 +72,16 @@ export const WorkspaceInviteTeam = () => { const { enqueueSnackBar } = useSnackBar(); const { sendInvitation } = useCreateWorkspaceInvitation(); - const { reset, handleSubmit, control, formState } = useForm({ - mode: 'onSubmit', - resolver: zodResolver(validationSchema()), - defaultValues: { - emails: '', + const { reset, handleSubmit, control, formState, watch } = useForm( + { + mode: 'onSubmit', + resolver: zodResolver(validationSchema()), + defaultValues: { + emails: '', + }, }, - }); + ); + const isEmailsEmpty = !watch('emails'); const submit = handleSubmit(async ({ emails }) => { const emailsList = sanitizeEmailList(emails.split(',')); @@ -109,7 +112,7 @@ export const WorkspaceInviteTeam = () => { } }; - const { isSubmitSuccessful } = formState; + const { isSubmitSuccessful, errors } = formState; useEffect(() => { if (isSubmitSuccessful) { @@ -144,6 +147,7 @@ export const WorkspaceInviteTeam = () => { accent="blue" title="Invite" type="submit" + disabled={isEmailsEmpty || !!errors.emails} /> diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx index c18292c4c..a45227408 100644 --- a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx @@ -87,125 +87,116 @@ export const SettingsDevelopersWebhooksDetail = () => { navigate(developerPath); }; + if (!webhookData?.targetUrl) { + return <>; + } + return ( - <> - {webhookData?.targetUrl && ( - { - navigate(developerPath); + { + navigate(developerPath); + }} + onSave={handleSave} + /> + } + > + +
+ + +
+
+ +