diff --git a/package.json b/package.json index cc9e84de47..2bbe5f5608 100644 --- a/package.json +++ b/package.json @@ -241,6 +241,7 @@ "@types/supertest": "^2.0.11", "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/experimental-utils": "^5.62.0", "@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/utils": "^6.9.1", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/packages/twenty-front/.eslintrc.cjs b/packages/twenty-front/.eslintrc.cjs index 2eb970413f..1b18512621 100644 --- a/packages/twenty-front/.eslintrc.cjs +++ b/packages/twenty-front/.eslintrc.cjs @@ -22,6 +22,7 @@ module.exports = { '**/*config.js', 'codegen*', 'tsup.ui.index.tsx', + '__mocks__', ], rules: { 'no-restricted-imports': [ @@ -48,6 +49,7 @@ module.exports = { '@nx/workspace-styled-components-prefixed-with-styled': 'error', '@nx/workspace-no-state-useref': 'error', '@nx/workspace-component-props-naming': 'error', + '@nx/workspace-explicit-boolean-predicates-in-if': 'error', '@nx/workspace-use-getLoadable-and-getValue-to-get-atoms': 'error', 'react/no-unescaped-entities': 'off', @@ -75,7 +77,7 @@ module.exports = { { files: ['*.ts', '*.tsx', '*.js', '*.jsx'], parserOptions: { - project: ['packages/twenty-front/tsconfig.*?.json'], + project: ['packages/twenty-front/tsconfig.{json,*.json}'], }, rules: {}, }, diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 9539629bfe..ec0ff28826 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -17,7 +17,7 @@ "lint:ci": "yarn lint --config .eslintrc-ci.cjs", "fmt:fix": "prettier --cache --write \"src/**/*.ts\" \"src/**/*.tsx\"", "fmt": "prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"", - "test": "jest", + "test": "jest src/modules/spreadsheet-import/utils/__tests__/dataMutations.test.ts", "test-watch": "jest --watch", "tsup": "tsup", "coverage": "jest --coverage", diff --git a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/effect-components/PageChangeEffect.tsx index 54c052c7a5..c5d8b32f30 100644 --- a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/effect-components/PageChangeEffect.tsx @@ -18,6 +18,8 @@ import { IconCheckbox } from '@/ui/display/icon'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useGetWorkspaceFromInviteHashLazyQuery } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { useIsMatchingLocation } from '../hooks/useIsMatchingLocation'; @@ -81,13 +83,13 @@ export const PageChangeEffect = () => { ) { navigate(AppPath.SignIn); } else if ( - onboardingStatus && + isNonNullable(onboardingStatus) && onboardingStatus === OnboardingStatus.Incomplete && !isMatchingLocation(AppPath.PlanRequired) ) { navigate(AppPath.PlanRequired); } else if ( - onboardingStatus && + isNonNullable(onboardingStatus) && [OnboardingStatus.Unpaid, OnboardingStatus.Canceled].includes( onboardingStatus, ) && @@ -122,7 +124,7 @@ export const PageChangeEffect = () => { inviteHash, }, onCompleted: (data) => { - if (!data.findWorkspaceFromInviteHash) { + if (isNullable(data.findWorkspaceFromInviteHash)) { navigateToSignUp(); } }, diff --git a/packages/twenty-front/src/modules/activities/blocks/FileBlock.tsx b/packages/twenty-front/src/modules/activities/blocks/FileBlock.tsx index 09052ec987..12dd9b29ce 100644 --- a/packages/twenty-front/src/modules/activities/blocks/FileBlock.tsx +++ b/packages/twenty-front/src/modules/activities/blocks/FileBlock.tsx @@ -8,9 +8,12 @@ import { } from '@blocknote/core'; import { createReactBlockSpec } from '@blocknote/react'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { Button } from '@/ui/input/button/components/Button'; import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { AttachmentIcon } from '../files/components/AttachmentIcon'; import { AttachmentType } from '../files/types/Attachment'; @@ -77,7 +80,7 @@ const FileBlockRenderer = ({ const inputFileRef = useRef(null); const handleUploadAttachment = async (file: File) => { - if (!file) { + if (isNullable(file)) { return ''; } const fileUrl = await editor.uploadFile?.(file); @@ -93,10 +96,11 @@ const FileBlockRenderer = ({ inputFileRef?.current?.click?.(); }; const handleFileChange = (e: ChangeEvent) => { - if (e.target.files) handleUploadAttachment?.(e.target.files[0]); + if (isNonNullable(e.target.files)) + handleUploadAttachment?.(e.target.files[0]); }; - if (block.props.url) { + if (isNonEmptyString(block.props.url)) { return ( diff --git a/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts b/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts index 6150037e8d..8ae8d1a3de 100644 --- a/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts +++ b/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts @@ -1,4 +1,5 @@ import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; +import { isNonNullable } from '~/utils/isNonNullable'; import { sortAsc } from '~/utils/sort'; export const sortCalendarEventsAsc = ( @@ -10,7 +11,11 @@ export const sortCalendarEventsAsc = ( calendarEventB.startsAt.getTime(), ); - if (startsAtSort === 0 && calendarEventA.endsAt && calendarEventB.endsAt) { + if ( + startsAtSort === 0 && + isNonNullable(calendarEventA.endsAt) && + isNonNullable(calendarEventB.endsAt) + ) { return sortAsc( calendarEventA.endsAt.getTime(), calendarEventB.endsAt.getTime(), diff --git a/packages/twenty-front/src/modules/activities/components/ActivityBodyEditor.tsx b/packages/twenty-front/src/modules/activities/components/ActivityBodyEditor.tsx index fc2408ba91..71ca1afb3a 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityBodyEditor.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityBodyEditor.tsx @@ -25,6 +25,8 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { FileFolder, useUploadFileMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { blockSpecs } from '../blocks/blockSpecs'; import { getSlashMenu } from '../blocks/slashMenu'; @@ -78,7 +80,7 @@ export const ActivityBodyEditor = ({ const { upsertActivity } = useUpsertActivity(); const persistBodyDebounced = useDebouncedCallback((newBody: string) => { - if (activity) { + if (isNonNullable(activity)) { upsertActivity({ activity, input: { @@ -90,7 +92,7 @@ export const ActivityBodyEditor = ({ const persistTitleAndBodyDebounced = useDebouncedCallback( (newTitle: string, newBody: string) => { - if (activity) { + if (isNonNullable(activity)) { upsertActivity({ activity, input: { @@ -124,7 +126,7 @@ export const ActivityBodyEditor = ({ const [uploadFile] = useUploadFileMutation(); const handleUploadAttachment = async (file: File): Promise => { - if (!file) { + if (isNullable(file)) { return ''; } const result = await uploadFile({ @@ -226,7 +228,7 @@ export const ActivityBodyEditor = ({ if (isNonEmptyString(activityBody) && activityBody !== '{}') { return JSON.parse(activityBody); } else if ( - activity && + isNonNullable(activity) && isNonEmptyString(activity.body) && activity?.body !== '{}' ) { @@ -251,7 +253,7 @@ export const ActivityBodyEditor = ({ const handleImagePaste = async (event: ClipboardEvent) => { const clipboardItems = event.clipboardData?.items; - if (clipboardItems) { + if (isNonNullable(clipboardItems)) { for (let i = 0; i < clipboardItems.length; i++) { if (clipboardItems[i].kind === 'file') { const isImage = clipboardItems[i].type.match('^image/'); @@ -266,7 +268,7 @@ export const ActivityBodyEditor = ({ return; } - if (isImage) { + if (isNonNullable(isImage)) { editor?.insertBlocks( [ { @@ -332,7 +334,7 @@ export const ActivityBodyEditor = ({ const currentBlockContent = blockIdentifier?.content; if ( - currentBlockContent && + isNonNullable(currentBlockContent) && isArray(currentBlockContent) && currentBlockContent.length === 0 ) { @@ -344,7 +346,7 @@ export const ActivityBodyEditor = ({ } if ( - currentBlockContent && + isNonNullable(currentBlockContent) && isArray(currentBlockContent) && currentBlockContent[0] && currentBlockContent[0].type === 'text' diff --git a/packages/twenty-front/src/modules/activities/components/ActivityBodyEffect.tsx b/packages/twenty-front/src/modules/activities/components/ActivityBodyEffect.tsx index 7da880ed18..8e244b30ce 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityBodyEffect.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityBodyEffect.tsx @@ -3,6 +3,7 @@ import { useRecoilState } from 'recoil'; import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { isNonNullable } from '~/utils/isNonNullable'; export const ActivityBodyEffect = ({ activityId }: { activityId: string }) => { const [activityFromStore] = useRecoilState( @@ -16,7 +17,7 @@ export const ActivityBodyEffect = ({ activityId }: { activityId: string }) => { useEffect(() => { if ( activityBody === '' && - activityFromStore && + isNonNullable(activityFromStore) && activityBody !== activityFromStore.body ) { setActivityBody(activityFromStore.body); diff --git a/packages/twenty-front/src/modules/activities/components/ActivityEditorEffect.tsx b/packages/twenty-front/src/modules/activities/components/ActivityEditorEffect.tsx index cffc5e0799..29c98e4ecd 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityEditorEffect.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityEditorEffect.tsx @@ -11,6 +11,7 @@ import { Activity } from '@/activities/types/Activity'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; +import { isNonNullable } from '~/utils/isNonNullable'; export const ActivityEditorEffect = ({ activityId, @@ -57,7 +58,7 @@ export const ActivityEditorEffect = ({ return; } - if (isActivityInCreateMode && activity) { + if (isActivityInCreateMode && isNonNullable(activity)) { if (canCreateActivity) { upsertActivity({ activity, @@ -71,7 +72,7 @@ export const ActivityEditorEffect = ({ } set(isActivityInCreateModeState, false); - } else if (activity) { + } else if (isNonNullable(activity)) { if ( activity.title !== activityTitle || activity.body !== activityBody diff --git a/packages/twenty-front/src/modules/activities/components/ActivityEditorFields.tsx b/packages/twenty-front/src/modules/activities/components/ActivityEditorFields.tsx index 126ce0de53..97d0a64a63 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityEditorFields.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityEditorFields.tsx @@ -13,6 +13,7 @@ import { import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledPropertyBox = styled(PropertyBox)` padding: 0; @@ -35,7 +36,7 @@ export const ActivityEditorFields = ({ const upsertActivityMutation = async ({ variables, }: RecordUpdateHookParams) => { - if (activityFromStore) { + if (isNonNullable(activityFromStore)) { await upsertActivity({ activity: activityFromStore as Activity, input: variables.updateOneRecordInput, diff --git a/packages/twenty-front/src/modules/activities/components/ActivityTitleEffect.tsx b/packages/twenty-front/src/modules/activities/components/ActivityTitleEffect.tsx index c5fcadaa7c..efcdaf4e95 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityTitleEffect.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityTitleEffect.tsx @@ -3,6 +3,7 @@ import { useRecoilState } from 'recoil'; import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { isNonNullable } from '~/utils/isNonNullable'; export const ActivityTitleEffect = ({ activityId }: { activityId: string }) => { const [activityFromStore] = useRecoilState( @@ -16,7 +17,7 @@ export const ActivityTitleEffect = ({ activityId }: { activityId: string }) => { useEffect(() => { if ( activityTitle === '' && - activityFromStore && + isNonNullable(activityFromStore) && activityTitle !== activityFromStore.title ) { setActivityTitle(activityFromStore.title); diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx index 3704145433..b6eb8a0975 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -33,6 +33,7 @@ import { TimelineThread, TimelineThreadsWithTotal, } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledContainer = styled.div` display: flex; @@ -140,7 +141,7 @@ export const EmailThreads = ({ } }; - if (error) { + if (isNonNullable(error)) { enqueueSnackBar(error.message || 'Error loading email threads', { variant: 'error', }); diff --git a/packages/twenty-front/src/modules/activities/emails/utils/getDisplayNameFromParticipant.ts b/packages/twenty-front/src/modules/activities/emails/utils/getDisplayNameFromParticipant.ts index 8735711c21..a1e50d46b2 100644 --- a/packages/twenty-front/src/modules/activities/emails/utils/getDisplayNameFromParticipant.ts +++ b/packages/twenty-front/src/modules/activities/emails/utils/getDisplayNameFromParticipant.ts @@ -1,4 +1,7 @@ +import { isNonEmptyString } from '@sniptt/guards'; + import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant'; +import { isNonNullable } from '~/utils/isNonNullable'; export const getDisplayNameFromParticipant = ({ participant, @@ -7,14 +10,14 @@ export const getDisplayNameFromParticipant = ({ participant: EmailThreadMessageParticipant; shouldUseFullName?: boolean; }) => { - if (participant.person) { + if (isNonNullable(participant.person)) { return ( `${participant.person?.name?.firstName}` + (shouldUseFullName ? ` ${participant.person?.name?.lastName}` : '') ); } - if (participant.workspaceMember) { + if (isNonNullable(participant.workspaceMember)) { return ( participant.workspaceMember?.name?.firstName + (shouldUseFullName @@ -23,11 +26,11 @@ export const getDisplayNameFromParticipant = ({ ); } - if (participant.displayName) { + if (isNonEmptyString(participant.displayName)) { return participant.displayName; } - if (participant.handle) { + if (isNonEmptyString(participant.handle)) { return participant.handle; } diff --git a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx index d13871b409..ffaaf4ddea 100644 --- a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx @@ -16,6 +16,7 @@ import { AnimatedPlaceholderEmptyTextContainer, AnimatedPlaceholderEmptyTitle, } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledAttachmentsContainer = styled.div` display: flex; @@ -46,7 +47,7 @@ export const Attachments = ({ const [isDraggingFile, setIsDraggingFile] = useState(false); const handleFileChange = (e: ChangeEvent) => { - if (e.target.files) onUploadFile?.(e.target.files[0]); + if (isNonNullable(e.target.files)) onUploadFile?.(e.target.files[0]); }; const handleUploadFileClick = () => { diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts index 9cde4d7439..31300e434e 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts @@ -49,7 +49,7 @@ export const useOpenCreateActivityDrawerForSelectedRowIds = ( }) .filter(isNonNullable); - if (relatedEntities) { + if (isNonNullable(relatedEntities)) { activityTargetableObjectArray = activityTargetableObjectArray.concat(relatedEntities); } diff --git a/packages/twenty-front/src/modules/activities/hooks/useUpsertActivity.ts b/packages/twenty-front/src/modules/activities/hooks/useUpsertActivity.ts index 746735dad9..169586a63c 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useUpsertActivity.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useUpsertActivity.ts @@ -112,7 +112,10 @@ export const useUpsertActivity = () => { } // Call optimistic effects - if (weAreOnObjectShowPage && objectShowPageTargetableObject) { + if ( + weAreOnObjectShowPage && + isNonNullable(objectShowPageTargetableObject) + ) { injectIntoTimelineActivitiesQueries({ timelineTargetableObject: objectShowPageTargetableObject, activityToInject: activityWithConnection, diff --git a/packages/twenty-front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx b/packages/twenty-front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx index 397255487d..a6653130a9 100644 --- a/packages/twenty-front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx +++ b/packages/twenty-front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx @@ -1,6 +1,6 @@ import { useLocation } from 'react-router-dom'; import styled from '@emotion/styled'; -import { isNonEmptyArray } from '@sniptt/guards'; +import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache'; @@ -108,91 +108,87 @@ export const ActivityActionBar = () => { setIsRightDrawerOpen(false); - if (viewableActivityId) { + if (isNonEmptyString(viewableActivityId)) { if ( isActivityInCreateMode && isNonNullable(temporaryActivityForEditor) ) { deleteActivityFromCache(temporaryActivityForEditor); setTemporaryActivityForEditor(null); - } else { - if (activityIdInDrawer) { - const activityTargetIdsToDelete: string[] = - activityTargets.map(mapToRecordId) ?? []; + } else if (isNonEmptyString(activityIdInDrawer)) { + const activityTargetIdsToDelete: string[] = + activityTargets.map(mapToRecordId) ?? []; - if (weAreOnTaskPage) { + if (weAreOnTaskPage) { + removeFromActivitiesQueries({ + activityIdToRemove: viewableActivityId, + targetableObjects: [], + activitiesFilters: currentCompletedTaskQueryVariables?.filter, + activitiesOrderByVariables: + currentCompletedTaskQueryVariables?.orderBy, + }); + + removeFromActivitiesQueries({ + activityIdToRemove: viewableActivityId, + targetableObjects: [], + activitiesFilters: currentIncompleteTaskQueryVariables?.filter, + activitiesOrderByVariables: + currentIncompleteTaskQueryVariables?.orderBy, + }); + } else if ( + weAreOnObjectShowPage && + isNonNullable(objectShowPageTargetableObject) + ) { + removeFromActivitiesQueries({ + activityIdToRemove: viewableActivityId, + targetableObjects: [objectShowPageTargetableObject], + activitiesFilters: {}, + activitiesOrderByVariables: + FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, + }); + + if (isNonNullable(currentCompletedTaskQueryVariables)) { removeFromActivitiesQueries({ activityIdToRemove: viewableActivityId, - targetableObjects: [], + targetableObjects: [objectShowPageTargetableObject], activitiesFilters: currentCompletedTaskQueryVariables?.filter, activitiesOrderByVariables: currentCompletedTaskQueryVariables?.orderBy, }); + } + if (isNonNullable(currentIncompleteTaskQueryVariables)) { removeFromActivitiesQueries({ activityIdToRemove: viewableActivityId, - targetableObjects: [], + targetableObjects: [objectShowPageTargetableObject], activitiesFilters: currentIncompleteTaskQueryVariables?.filter, activitiesOrderByVariables: currentIncompleteTaskQueryVariables?.orderBy, }); - } else if ( - weAreOnObjectShowPage && - isNonNullable(objectShowPageTargetableObject) - ) { + } + + if (isNonNullable(currentNotesQueryVariables)) { removeFromActivitiesQueries({ activityIdToRemove: viewableActivityId, targetableObjects: [objectShowPageTargetableObject], - activitiesFilters: {}, + activitiesFilters: currentNotesQueryVariables?.filter, activitiesOrderByVariables: - FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, - }); - - if (isNonNullable(currentCompletedTaskQueryVariables)) { - removeFromActivitiesQueries({ - activityIdToRemove: viewableActivityId, - targetableObjects: [objectShowPageTargetableObject], - activitiesFilters: - currentCompletedTaskQueryVariables?.filter, - activitiesOrderByVariables: - currentCompletedTaskQueryVariables?.orderBy, - }); - } - - if (isNonNullable(currentIncompleteTaskQueryVariables)) { - removeFromActivitiesQueries({ - activityIdToRemove: viewableActivityId, - targetableObjects: [objectShowPageTargetableObject], - activitiesFilters: - currentIncompleteTaskQueryVariables?.filter, - activitiesOrderByVariables: - currentIncompleteTaskQueryVariables?.orderBy, - }); - } - - if (isNonNullable(currentNotesQueryVariables)) { - removeFromActivitiesQueries({ - activityIdToRemove: viewableActivityId, - targetableObjects: [objectShowPageTargetableObject], - activitiesFilters: currentNotesQueryVariables?.filter, - activitiesOrderByVariables: - currentNotesQueryVariables?.orderBy, - }); - } - - removeFromActivityTargetsQueries({ - activityTargetsToRemove: activity?.activityTargets ?? [], - targetableObjects: [objectShowPageTargetableObject], + currentNotesQueryVariables?.orderBy, }); } - if (isNonEmptyArray(activityTargetIdsToDelete)) { - await deleteManyActivityTargets(activityTargetIdsToDelete); - } - - await deleteOneActivity?.(viewableActivityId); + removeFromActivityTargetsQueries({ + activityTargetsToRemove: activity?.activityTargets ?? [], + targetableObjects: [objectShowPageTargetableObject], + }); } + + if (isNonEmptyArray(activityTargetIdsToDelete)) { + await deleteManyActivityTargets(activityTargetIdsToDelete); + } + + await deleteOneActivity?.(viewableActivityId); } } }, @@ -223,10 +219,13 @@ export const ActivityActionBar = () => { const addActivity = () => { setIsRightDrawerOpen(false); - if (record && objectShowPageTargetableObject) { + if ( + isNonNullable(record) && + isNonNullable(objectShowPageTargetableObject) + ) { openCreateActivity({ - type: record.type, - customAssignee: record.assignee, + type: record?.type, + customAssignee: record?.assignee, targetableObjects: activityTargetableEntityArray, }); } diff --git a/packages/twenty-front/src/modules/activities/timeline/utils/groupActivitiesByMonth.ts b/packages/twenty-front/src/modules/activities/timeline/utils/groupActivitiesByMonth.ts index c93550dc03..a773cd1714 100644 --- a/packages/twenty-front/src/modules/activities/timeline/utils/groupActivitiesByMonth.ts +++ b/packages/twenty-front/src/modules/activities/timeline/utils/groupActivitiesByMonth.ts @@ -1,4 +1,5 @@ import { Activity } from '@/activities/types/Activity'; +import { isNonNullable } from '~/utils/isNonNullable'; export type ActivityForActivityGroup = Pick; @@ -21,7 +22,7 @@ export const groupActivitiesByMonth = ( const matchingGroup = acitivityGroups.find( (x) => x.year === year && x.month === month, ); - if (matchingGroup) { + if (isNonNullable(matchingGroup)) { matchingGroup.items.push(activity); } else { acitivityGroups.push({ diff --git a/packages/twenty-front/src/modules/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects.ts b/packages/twenty-front/src/modules/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects.ts index 1f30e422af..23427302f0 100644 --- a/packages/twenty-front/src/modules/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects.ts +++ b/packages/twenty-front/src/modules/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects.ts @@ -1,3 +1,5 @@ +import { isNonNullable } from '~/utils/isNonNullable'; + import { ActivityTargetableObject } from '../types/ActivityTargetableEntity'; export const flattenTargetableObjectsAndTheirRelatedTargetableObjects = ( @@ -9,7 +11,7 @@ export const flattenTargetableObjectsAndTheirRelatedTargetableObjects = ( []) { flattenedTargetableObjects.push(targetableObject); - if (targetableObject.relatedTargetableObjects) { + if (isNonNullable(targetableObject.relatedTargetableObjects)) { for (const relatedEntity of targetableObject.relatedTargetableObjects ?? []) { flattenedTargetableObjects.push(relatedEntity); diff --git a/packages/twenty-front/src/modules/activities/utils/getActivitySummary.ts b/packages/twenty-front/src/modules/activities/utils/getActivitySummary.ts index 88b27c9008..157cf0e45d 100644 --- a/packages/twenty-front/src/modules/activities/utils/getActivitySummary.ts +++ b/packages/twenty-front/src/modules/activities/utils/getActivitySummary.ts @@ -1,4 +1,4 @@ -import { isArray } from '@sniptt/guards'; +import { isArray, isNonEmptyString } from '@sniptt/guards'; export const getActivitySummary = (activityBody: string) => { const noteBody = activityBody ? JSON.parse(activityBody) : []; @@ -13,7 +13,7 @@ export const getActivitySummary = (activityBody: string) => { return ''; } - if (firstNoteBlockContent.text) { + if (isNonEmptyString(firstNoteBlockContent.text)) { return noteBody[0].content.text; } diff --git a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts index cd2b62704f..8c11296557 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts @@ -9,6 +9,7 @@ import { AppPath } from '@/types/AppPath'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; +import { isNonNullable } from '~/utils/isNonNullable'; import { ApolloFactory } from '../services/apollo.factory'; @@ -57,7 +58,7 @@ export const useApolloFactory = () => { }, [setTokenPair, isDebugMode]); useUpdateEffect(() => { - if (apolloRef.current) { + if (isNonNullable(apolloRef.current)) { apolloRef.current.updateTokenPair(tokenPair); } }, [tokenPair]); diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts index ec4061eef9..14d756c8ce 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts @@ -1,4 +1,5 @@ import { ApolloCache, StoreObject } from '@apollo/client'; +import { isNonEmptyString } from '@sniptt/guards'; import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; @@ -76,7 +77,7 @@ export const triggerCreateRecordsOptimisticEffect = ({ const hasAddedRecords = recordsToCreate .map((recordToCreate) => { - if (recordToCreate.id) { + if (isNonEmptyString(recordToCreate.id)) { const recordToCreateReference = toReference(recordToCreate); if (!recordToCreateReference) { diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts index aa9eac813a..a0a7ca2d15 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -136,7 +136,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({ } const shouldAttachSourceToAllTargets = - updatedSourceRecord && targetRecordsToAttachTo.length; + isNonNullable(updatedSourceRecord) && targetRecordsToAttachTo.length > 0; if (shouldAttachSourceToAllTargets) { targetRecordsToAttachTo.forEach((targetRecordToAttachTo) => diff --git a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts index 23b4612f5b..1887837dc9 100644 --- a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts +++ b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts @@ -78,7 +78,7 @@ export class ApolloFactory implements ApolloManager { }); const errorLink = onError( ({ graphQLErrors, networkError, forward, operation }) => { - if (graphQLErrors) { + if (isNonNullable(graphQLErrors)) { onErrorCb?.(graphQLErrors); for (const graphQLError of graphQLErrors) { @@ -86,7 +86,9 @@ export class ApolloFactory implements ApolloManager { return fromPromise( renewToken(uri, this.tokenPair) .then((tokens) => { - onTokenPairChange?.(tokens); + if (isNonNullable(tokens)) { + onTokenPairChange?.(tokens); + } }) .catch(() => { onUnauthenticatedError?.(); @@ -99,7 +101,9 @@ export class ApolloFactory implements ApolloManager { return fromPromise( renewToken(uri, this.tokenPair) .then((tokens) => { - onTokenPairChange?.(tokens); + if (isNonNullable(tokens)) { + onTokenPairChange?.(tokens); + } }) .catch(() => { onUnauthenticatedError?.(); @@ -107,7 +111,7 @@ export class ApolloFactory implements ApolloManager { ).flatMap(() => forward(operation)); } default: - if (isDebugMode) { + if (isDebugMode === true) { logDebug( `[GraphQL error]: Message: ${ graphQLError.message @@ -122,8 +126,8 @@ export class ApolloFactory implements ApolloManager { } } - if (networkError) { - if (isDebugMode) { + if (isNonNullable(networkError)) { + if (isDebugMode === true) { logDebug(`[Network error]: ${networkError}`); } onNetworkError?.(networkError); diff --git a/packages/twenty-front/src/modules/apollo/utils/index.ts b/packages/twenty-front/src/modules/apollo/utils/index.ts index d182601aa1..1aa3c60fa6 100644 --- a/packages/twenty-front/src/modules/apollo/utils/index.ts +++ b/packages/twenty-front/src/modules/apollo/utils/index.ts @@ -1,5 +1,6 @@ import { ApolloLink, gql, Operation } from '@apollo/client'; +import { isNonNullable } from '~/utils/isNonNullable'; import { logDebug } from '~/utils/logDebug'; import { logError } from '~/utils/logError'; @@ -64,7 +65,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) => getGroup(!hasError)(...titleArgs); - if (errors) { + if (isNonNullable(errors)) { errors.forEach((err: any) => { logDebug( `%c${err.message}`, @@ -82,10 +83,10 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) => logDebug('QUERY', query); - if (result.data) { + if (isNonNullable(result.data)) { logDebug('RESULT', result.data); } - if (errors) { + if (isNonNullable(errors)) { logDebug('ERRORS', errors); } @@ -95,7 +96,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) => logDebug( `${operationType} ${schemaName}::${queryName} (in ${time} ms)`, ); - if (errors) { + if (isNonNullable(errors)) { logError(errors); } } diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 33c1170d85..85c11d2679 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -26,6 +26,7 @@ import { useSignUpMutation, useVerifyMutation, } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; import { currentUserState } from '../states/currentUserState'; import { tokenPairState } from '../states/tokenPairState'; @@ -59,7 +60,7 @@ export const useAuth = () => { }, }); - if (challengeResult.errors) { + if (isNonNullable(challengeResult.errors)) { throw challengeResult.errors; } @@ -78,7 +79,7 @@ export const useAuth = () => { variables: { loginToken }, }); - if (verifyResult.errors) { + if (isNonNullable(verifyResult.errors)) { throw verifyResult.errors; } @@ -91,7 +92,7 @@ export const useAuth = () => { const user = verifyResult.data?.verify.user; let workspaceMember = null; setCurrentUser(user); - if (user.workspaceMember) { + if (isNonNullable(user.workspaceMember)) { workspaceMember = { ...user.workspaceMember, colorScheme: user.workspaceMember?.colorScheme as ColorScheme, @@ -183,7 +184,7 @@ export const useAuth = () => { }, }); - if (signUpResult.errors) { + if (isNonNullable(signUpResult.errors)) { throw signUpResult.errors; } diff --git a/packages/twenty-front/src/modules/auth/services/AuthService.ts b/packages/twenty-front/src/modules/auth/services/AuthService.ts index d39338d509..cc56547c10 100644 --- a/packages/twenty-front/src/modules/auth/services/AuthService.ts +++ b/packages/twenty-front/src/modules/auth/services/AuthService.ts @@ -13,6 +13,8 @@ import { RenewTokenMutation, RenewTokenMutationVariables, } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; const logger = loggerLink(() => 'Twenty-Refresh'); @@ -45,7 +47,7 @@ const renewTokenMutation = async ( fetchPolicy: 'network-only', }); - if (errors || !data) { + if (isNonNullable(errors) || isNullable(data)) { throw new Error('Something went wrong during token renewal'); } @@ -67,5 +69,5 @@ export const renewToken = async ( const data = await renewTokenMutation(uri, tokenPair.refreshToken.token); - return data.renewToken.tokens; + return data?.renewToken.tokens; }; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts index b4dd9f67ec..8f13a8c4c7 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts @@ -22,7 +22,7 @@ export const useHandleResetPassword = () => { variables: { email }, }); - if (data?.emailPasswordResetLink?.success) { + if (data?.emailPasswordResetLink?.success === true) { enqueueSnackBar('Password reset link has been sent to the email', { variant: 'success', }); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts index 1b93836b03..ee090c1270 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts @@ -16,7 +16,7 @@ export const useNavigateAfterSignInUp = () => { currentWorkspaceMember: WorkspaceMember | null, ) => { if ( - billing?.isBillingEnabled && + billing?.isBillingEnabled === true && !['active', 'trialing'].includes(currentWorkspace.subscriptionStatus) ) { navigate(AppPath.PlanRequired); diff --git a/packages/twenty-front/src/modules/auth/utils/getOnboardingStatus.ts b/packages/twenty-front/src/modules/auth/utils/getOnboardingStatus.ts index 0ed3310413..e32e930d9a 100644 --- a/packages/twenty-front/src/modules/auth/utils/getOnboardingStatus.ts +++ b/packages/twenty-front/src/modules/auth/utils/getOnboardingStatus.ts @@ -37,7 +37,7 @@ export const getOnboardingStatus = ({ } if ( - isBillingEnabled && + isBillingEnabled === true && currentWorkspace.subscriptionStatus === 'incomplete' ) { return OnboardingStatus.Incomplete; @@ -54,15 +54,24 @@ export const getOnboardingStatus = ({ return OnboardingStatus.OngoingProfileCreation; } - if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') { + if ( + isBillingEnabled === true && + currentWorkspace.subscriptionStatus === 'canceled' + ) { return OnboardingStatus.Canceled; } - if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'past_due') { + if ( + isBillingEnabled === true && + currentWorkspace.subscriptionStatus === 'past_due' + ) { return OnboardingStatus.PastDue; } - if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'unpaid') { + if ( + isBillingEnabled === true && + currentWorkspace.subscriptionStatus === 'unpaid' + ) { return OnboardingStatus.Unpaid; } diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx index f665c2b4e3..75aa82669d 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -10,6 +10,7 @@ import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { telemetryState } from '@/client-config/states/telemetryState'; import { useGetClientConfigQuery } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; export const ClientConfigProvider: React.FC = ({ children, @@ -29,7 +30,7 @@ export const ClientConfigProvider: React.FC = ({ const { data, loading } = useGetClientConfigQuery(); useEffect(() => { - if (data?.clientConfig) { + if (isNonNullable(data?.clientConfig)) { setAuthProviders({ google: data?.clientConfig.authProviders.google, password: data?.clientConfig.authProviders.password, diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx index b5a9a164fd..3cd35214ff 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx @@ -1,5 +1,6 @@ import { useMemo, useRef } from 'react'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilState, useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; @@ -22,6 +23,7 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { Avatar } from '@/users/components/Avatar'; import { getLogoUrlFromDomainName } from '~/utils'; +import { isNonNullable } from '~/utils/isNonNullable'; import { useCommandMenu } from '../hooks/useCommandMenu'; import { commandMenuCommandsState } from '../states/commandMenuCommandsState'; @@ -218,7 +220,7 @@ export const CommandMenu = () => { }; const checkInLabels = (cmd: Command, search: string) => { - if (cmd.label) { + if (isNonEmptyString(cmd.label)) { return cmd.label.toLowerCase().includes(search.toLowerCase()); } return false; @@ -276,7 +278,7 @@ export const CommandMenu = () => { ...otherCommands, ].find((cmd) => cmd.id === itemId); - if (command) { + if (isNonNullable(command)) { const { to, onCommandClick } = command; onItemClick(onCommandClick, to); } diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx index 52e4a307c3..9c84e6601f 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx @@ -1,3 +1,4 @@ +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { IconArrowUpRight } from '@/ui/display/icon'; @@ -28,7 +29,7 @@ export const CommandMenuItem = ({ }: CommandMenuItemProps) => { const { onItemClick } = useCommandMenu(); - if (to && !Icon) { + if (isNonEmptyString(to) && !Icon) { Icon = IconArrowUpRight; } diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index 9e9944af74..249eb89d88 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -1,11 +1,13 @@ import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; +import { isNonNullable } from '~/utils/isNonNullable'; import { COMMAND_MENU_COMMANDS } from '../constants/CommandMenuCommands'; import { commandMenuCommandsState } from '../states/commandMenuCommandsState'; @@ -75,11 +77,11 @@ export const useCommandMenu = () => { (onClick?: () => void, to?: string) => { toggleCommandMenu(); - if (onClick) { + if (isNonNullable(onClick)) { onClick(); return; } - if (to) { + if (isNonEmptyString(to)) { navigate(to); return; } diff --git a/packages/twenty-front/src/modules/error-handler/components/SentryInitiEffect.tsx b/packages/twenty-front/src/modules/error-handler/components/SentryInitiEffect.tsx index cd8160e4f0..ba7e1978df 100644 --- a/packages/twenty-front/src/modules/error-handler/components/SentryInitiEffect.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/SentryInitiEffect.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import * as Sentry from '@sentry/react'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; @@ -7,6 +8,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; +import { isNonNullable } from '~/utils/isNonNullable'; export const SentryInitEffect = () => { const sentryConfig = useRecoilValue(sentryConfigState); @@ -18,7 +20,7 @@ export const SentryInitEffect = () => { const [isSentryInitialized, setIsSentryInitialized] = useState(false); useEffect(() => { - if (sentryConfig?.dsn && !isSentryInitialized) { + if (isNonEmptyString(sentryConfig?.dsn) && !isSentryInitialized) { Sentry.init({ dsn: sentryConfig?.dsn, integrations: [ @@ -38,7 +40,7 @@ export const SentryInitEffect = () => { setIsSentryInitialized(true); } - if (currentUser) { + if (isNonNullable(currentUser)) { Sentry.setUser({ email: currentUser?.email, id: currentUser?.id, diff --git a/packages/twenty-front/src/modules/object-metadata/components/ApolloMetadataClientProvider.tsx b/packages/twenty-front/src/modules/object-metadata/components/ApolloMetadataClientProvider.tsx index de90c844b8..2af8362986 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ApolloMetadataClientProvider.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ApolloMetadataClientProvider.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { ApolloClient, InMemoryCache } from '@apollo/client'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilState } from 'recoil'; import { tokenPairState } from '@/auth/states/tokenPairState'; @@ -14,7 +15,7 @@ export const ApolloMetadataClientProvider = ({ }) => { const [tokenPair] = useRecoilState(tokenPairState); const apolloMetadataClient = useMemo(() => { - if (tokenPair?.accessToken.token) { + if (isNonEmptyString(tokenPair?.accessToken.token)) { return new ApolloClient({ uri: `${REACT_APP_SERVER_BASE_URL}/metadata`, cache: new InMemoryCache(), diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx index b8e1eb3d86..872c8da192 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx @@ -12,7 +12,7 @@ export const ObjectMetadataItemsProvider = ({ const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const shouldDisplayChildren = () => { - if (objectMetadataItems.length) { + if (objectMetadataItems.length > 0) { return true; } return !currentWorkspaceMember; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 7dcf29ab0f..2d6665d182 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -1,5 +1,6 @@ import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; import { ObjectMetadataItem } from '../types/ObjectMetadataItem'; @@ -31,7 +32,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({ } if (field.type === FieldMetadataType.Relation) { - if (field.fromRelationMetadata) { + if (isNonNullable(field.fromRelationMetadata)) { return acc; } } diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts b/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts index 0fc1ae8e86..20d41ff3f7 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts @@ -3,6 +3,7 @@ import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderByField } from '@/object-metadata/types/OrderByField'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; export const getObjectOrderByField = ( objectMetadataItem: ObjectMetadataItem, @@ -11,7 +12,7 @@ export const getObjectOrderByField = ( const labelIdentifierFieldMetadata = getLabelIdentifierFieldMetadataItem(objectMetadataItem); - if (labelIdentifierFieldMetadata) { + if (isNonNullable(labelIdentifierFieldMetadata)) { switch (labelIdentifierFieldMetadata.type) { case FieldMetadataType.FullName: return { diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts index 7253e25f46..9b6b45e817 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts @@ -3,6 +3,7 @@ import { gql, useApolloClient } from '@apollo/client'; import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const useGetRecordFromCache = ({ @@ -17,7 +18,7 @@ export const useGetRecordFromCache = ({ recordId: string, cache = apolloClient.cache, ) => { - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts index 3d87f6d4ea..8cbc5fe60b 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts @@ -3,6 +3,7 @@ import { Modifiers } from '@apollo/client/cache'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const useModifyRecordFromCache = ({ @@ -16,7 +17,7 @@ export const useModifyRecordFromCache = ({ recordId: string, fieldModifiers: Modifiers, ) => { - if (!objectMetadataItem) return; + if (isNullable(objectMetadataItem)) return; const cachedRecordId = cache.identify({ __typename: capitalize(objectMetadataItem.nameSingular), diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts index 426e4176f9..fa099e1077 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts @@ -5,6 +5,7 @@ import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMeta import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache'; import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isNonNullable } from '~/utils/isNonNullable'; export const useCreateManyRecordsInCache = ({ objectNameSingular, @@ -34,7 +35,7 @@ export const useCreateManyRecordsInCache = ({ const generatedCachedObjectRecord = generateObjectRecordOptimisticResponse(record); - if (generatedCachedObjectRecord) { + if (isNonNullable(generatedCachedObjectRecord)) { addRecordInCache(generatedCachedObjectRecord); createdRecordsInCache.push(generatedCachedObjectRecord); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index d3651828b8..22cbc2ec27 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -14,6 +14,7 @@ import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge'; import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { isNonNullable } from '~/utils/isNonNullable'; import { logError } from '~/utils/logError'; import { capitalize } from '~/utils/string/capitalize'; @@ -84,7 +85,7 @@ export const useFindManyRecords = ({ onCompleted?.(data[objectMetadataItem.namePlural], pageInfo); - if (data?.[objectMetadataItem.namePlural]) { + if (isNonNullable(data?.[objectMetadataItem.namePlural])) { setLastCursor(pageInfo.endCursor ?? ''); setHasNextPage(pageInfo.hasNextPage ?? false); } @@ -131,7 +132,7 @@ export const useFindManyRecords = ({ const pageInfo = fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo; - if (data?.[objectMetadataItem.namePlural]) { + if (isNonNullable(data?.[objectMetadataItem.namePlural])) { setLastCursor(pageInfo.endCursor ?? ''); setHasNextPage(pageInfo.hasNextPage ?? false); } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts index 1f9a92b497..3a66eb825c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts @@ -3,6 +3,7 @@ import { gql } from '@apollo/client'; import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getCreateManyRecordsMutationResponseField = ( @@ -16,7 +17,7 @@ export const useGenerateCreateManyRecordMutation = ({ }) => { const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts index cd607c83fa..9f2d1ccf57 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts @@ -3,6 +3,7 @@ import { gql } from '@apollo/client'; import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getCreateOneRecordMutationResponseField = ( @@ -16,7 +17,7 @@ export const useGenerateCreateOneRecordMutation = ({ }) => { const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts index af80fd6453..e528fea0a6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts @@ -2,6 +2,7 @@ import { gql } from '@apollo/client'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getDeleteManyRecordsMutationResponseField = ( @@ -13,7 +14,7 @@ export const useGenerateDeleteManyRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts index 743eee38f6..17a11dbfdc 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts @@ -3,6 +3,7 @@ import { gql } from '@apollo/client'; import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getExecuteQuickActionOnOneRecordMutationGraphQLField = ({ @@ -20,7 +21,7 @@ export const useGenerateExecuteQuickActionOnOneRecordMutation = ({ }) => { const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts index 8a00f1beef..a7da5cbc9f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts @@ -3,6 +3,7 @@ import { gql } from '@apollo/client'; import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getUpdateOneRecordMutationResponseField = ( @@ -16,7 +17,7 @@ export const useGenerateUpdateOneRecordMutation = ({ }) => { const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index c9d604d1d2..f3d1d9c95c 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -2,6 +2,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isNonNullable } from '~/utils/isNonNullable'; import { getOperandLabel } from '../utils/getOperandLabel'; import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; @@ -24,7 +25,10 @@ export const ObjectFilterDropdownOperandSelect = () => { setSelectedOperandInDropdown(newOperand); setIsObjectFilterDropdownOperandSelectUnfolded(false); - if (filterDefinitionUsedInDropdown && selectedFilter) { + if ( + isNonNullable(filterDefinitionUsedInDropdown) && + isNonNullable(selectedFilter) + ) { selectFilter?.({ fieldMetadataId: selectedFilter.fieldMetadataId, displayValue: selectedFilter.displayValue, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx index 00e8889ec3..5f09b75310 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx @@ -5,6 +5,7 @@ import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataIt import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useOptionsForSelect } from '@/object-record/object-filter-dropdown/hooks/useOptionsForSelect'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { isNonNullable } from '~/utils/isNonNullable'; export const EMPTY_FILTER_VALUE = ''; export const MAX_OPTIONS_TO_DISPLAY = 3; @@ -31,7 +32,7 @@ export const ObjectFilterDropdownOptionSelect = () => { >([]); useEffect(() => { - if (selectOptions) { + if (isNonNullable(selectOptions)) { const options = selectOptions.map((option) => { const isSelected = objectFilterDropdownSelectedOptionValues?.includes(option.value) ?? @@ -70,7 +71,10 @@ export const ObjectFilterDropdownOptionSelect = () => { ? `${selectedOptions.length} options` : selectedOptions.map((option) => option.label).join(', '); - if (filterDefinitionUsedInDropdown && selectedOperandInDropdown) { + if ( + isNonNullable(filterDefinitionUsedInDropdown) && + isNonNullable(selectedOperandInDropdown) + ) { const newFilterValue = selectedOptions.length > 0 ? JSON.stringify(selectedOptions.map((option) => option.value)) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index c711e872c7..a39105d28d 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -2,6 +2,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/ import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; +import { isNonNullable } from '~/utils/isNonNullable'; export const EMPTY_FILTER_VALUE = '[]'; export const MAX_RECORDS_TO_DISPLAY = 3; @@ -66,7 +67,10 @@ export const ObjectFilterDropdownRecordSelect = () => { ? `${selectedRecordNames.length} companies` : selectedRecordNames.join(', '); - if (filterDefinitionUsedInDropdown && selectedOperandInDropdown) { + if ( + isNonNullable(filterDefinitionUsedInDropdown) && + isNonNullable(selectedOperandInDropdown) + ) { const newFilterValue = newSelectedRecordIds.length > 0 ? JSON.stringify(newSelectedRecordIds) diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts index fde1df2537..e01b5885f8 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts @@ -2,6 +2,7 @@ import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderByField } from '@/object-metadata/types/OrderByField'; import { Field } from '~/generated/graphql'; import { mapArrayToObject } from '~/utils/array/mapArrayToObject'; +import { isNullable } from '~/utils/isNullable'; import { Sort } from '../types/Sort'; @@ -14,7 +15,7 @@ export const turnSortsIntoOrderBy = ( sorts.map((sort) => { const correspondingField = fieldsById[sort.fieldMetadataId]; - if (!correspondingField) { + if (isNullable(correspondingField)) { throw new Error( `Could not find field ${sort.fieldMetadataId} in metadata object`, ); diff --git a/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx b/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx index a609203749..3d7c972632 100644 --- a/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx +++ b/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx @@ -19,6 +19,7 @@ import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionB import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { isNonNullable } from '~/utils/isNonNullable'; type useRecordActionBarProps = { objectMetadataItem: ObjectMetadataItem; @@ -62,7 +63,7 @@ export const useRecordActionBar = ({ if (isFavorite) { deleteFavorite(foundFavorite.id); - } else if (selectedRecord) { + } else if (isNonNullable(selectedRecord)) { createFavorite(selectedRecord, objectMetadataItem.nameSingular); } callback?.(); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts index c2ab6ed95b..007734fe99 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts @@ -80,7 +80,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = true, ); - if (lastColumn) { + if (isNonNullable(lastColumn)) { set( isLastRecordBoardColumnFamilyStateScopeMap({ scopeId, @@ -100,7 +100,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = true, ); - if (firstColumn) { + if (isNonNullable(firstColumn)) { set( isFirstRecordBoardColumnFamilyStateScopeMap({ scopeId, diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useGetButtonIcon.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useGetButtonIcon.ts index 18ca7ecb39..3698421c95 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useGetButtonIcon.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useGetButtonIcon.ts @@ -3,6 +3,7 @@ import { useContext } from 'react'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { IconPencil } from '@/ui/display/icon'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; +import { isNullable } from '~/utils/isNullable'; import { FieldContext } from '../contexts/FieldContext'; import { isFieldEmail } from '../types/guards/isFieldEmail'; @@ -12,7 +13,7 @@ import { isFieldPhone } from '../types/guards/isFieldPhone'; export const useGetButtonIcon = (): IconComponent | undefined => { const { fieldDefinition } = useContext(FieldContext); - if (!fieldDefinition) return undefined; + if (isNullable(fieldDefinition)) return undefined; if ( isFieldLink(fieldDefinition) || diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx index 73697d6557..8907ff5771 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx @@ -9,6 +9,7 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { MenuItemSelectTag } from '@/ui/navigation/menu-item/components/MenuItemSelectTag'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledRelationPickerContainer = styled.div` left: -1px; @@ -52,7 +53,7 @@ export const SelectFieldInput = ({ event.target instanceof HTMLInputElement && event.target.tagName === 'INPUT' ); - if (weAreNotInAnHTMLInput && onCancel) { + if (weAreNotInAnHTMLInput && isNonNullable(onCancel)) { onCancel(); } }, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx index 712e2c46be..be7c3277fb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx @@ -74,7 +74,7 @@ const tabJestFn = fn(); const shiftTabJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { enterJestFn.mockClear(); escapeJestfn.mockClear(); clickOutsideJestFn.mockClear(); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx index ace77e5915..eabb331450 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/NumberFieldInput.stories.tsx @@ -74,7 +74,7 @@ const tabJestFn = fn(); const shiftTabJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { enterJestFn.mockClear(); escapeJestfn.mockClear(); clickOutsideJestFn.mockClear(); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx index 5245859abe..7de9b0ff02 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx @@ -75,7 +75,7 @@ const tabJestFn = fn(); const shiftTabJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { enterJestFn.mockClear(); escapeJestfn.mockClear(); clickOutsideJestFn.mockClear(); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RatingFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RatingFieldInput.stories.tsx index 7d148460d3..292bef437f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RatingFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RatingFieldInput.stories.tsx @@ -4,6 +4,7 @@ import { expect, fn, userEvent, waitFor, within } from '@storybook/test'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; import { FieldRatingValue } from '../../../../types/FieldMetadata'; import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; @@ -62,7 +63,7 @@ const RatingFieldInputWithContext = ({ const submitJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { submitJestFn.mockClear(); } return ; @@ -100,7 +101,7 @@ export const Submit: Story = { const firstStar = input.firstElementChild; await waitFor(() => { - if (firstStar) { + if (isNonNullable(firstStar)) { userEvent.click(firstStar); expect(submitJestFn).toHaveBeenCalledTimes(1); } diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx index 7690746902..3cbe87a498 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationFieldInput.stories.tsx @@ -88,7 +88,7 @@ const submitJestFn = fn(); const cancelJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { submitJestFn.mockClear(); cancelJestFn.mockClear(); } diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx index c89955dea6..91c9dc8e6e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/TextFieldInput.stories.tsx @@ -74,7 +74,7 @@ const tabJestFn = fn(); const shiftTabJestFn = fn(); const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { enterJestFn.mockClear(); escapeJestfn.mockClear(); clickOutsideJestFn.mockClear(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index ab0cbcbfa5..4632f039f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -136,7 +136,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( }); break; case ViewFilterOperand.IsNot: - if (parsedRecordIds.length) { + if (parsedRecordIds.length > 0) { objectRecordFilters.push({ not: { [correspondingField.name + 'Id']: { diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx index 31a4447b26..0ef9c35258 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx @@ -4,6 +4,7 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useViewBar } from '@/views/hooks/useViewBar'; +import { isNullable } from '~/utils/isNullable'; type RecordIndexViewBarEffectProps = { objectNamePlural: string; @@ -33,7 +34,7 @@ export const RecordIndexViewBarEffect = ({ } = useViewBar({ viewBarId }); useEffect(() => { - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return; } setViewObjectMetadataId?.(objectMetadataItem.id); diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts index 6d91fe33b5..80a2b9f60c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts @@ -6,6 +6,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { isNonNullable } from '~/utils/isNonNullable'; import { useFindManyParams } from '../../hooks/useLoadRecordIndexTable'; @@ -52,7 +53,7 @@ export const generateCsv: GenerateExport = ({ return hasSubFields; }); - if (fieldsWithSubFields) { + if (isNonNullable(fieldsWithSubFields)) { const nestedFieldsWithoutTypename = Object.keys( (fieldsWithSubFields as any)[column.field], ) diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx index fe1aef9c27..0b87709735 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx @@ -20,7 +20,7 @@ const StyledRecordInlineCellNormalModeOuterContainer = styled.div< padding: ${({ theme }) => theme.spacing(1)}; ${(props) => { - if (props.isHovered) { + if (props.isHovered === true) { return css` background-color: ${!props.disableHoverEffect ? props.theme.background.transparent.light diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts index 8f834fdbaa..0c6d8e782d 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts @@ -5,6 +5,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { isNonNullable } from '~/utils/isNonNullable'; import { isInlineCellInEditModeScopedState } from '../states/isInlineCellInEditModeScopedState'; import { InlineCellHotkeyScope } from '../types/InlineCellHotkeyScope'; @@ -39,7 +40,7 @@ export const useInlineCell = () => { setIsInlineCellInEditMode(true); initFieldInputDraftValue(); - if (customEditHotkeyScopeForField) { + if (isNonNullable(customEditHotkeyScopeForField)) { setHotkeyScopeAndMemorizePreviousScope( customEditHotkeyScopeForField.scope, customEditHotkeyScopeForField.customScopes, diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx index 98f4e4f13b..30d629fd75 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx @@ -31,6 +31,7 @@ import { useUploadImageMutation, } from '~/generated/graphql'; import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; type RecordShowContainerProps = { objectNameSingular: string; @@ -92,7 +93,7 @@ export const RecordShowContainer = ({ if (!avatarUrl) { return; } - if (!updateOneRecord) { + if (isNullable(updateOneRecord)) { return; } if (!recordFromStore) { diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx index 85e8de1d12..5d85a71932 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx @@ -15,6 +15,7 @@ import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkey import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; +import { isNullable } from '~/utils/isNullable'; const StyledContainer = styled.td<{ isSelected: boolean }>` background: ${({ isSelected, theme }) => @@ -45,7 +46,7 @@ export const RecordTableCellContainer = () => { const updateRecord = useContext(RecordUpdateContext); - if (!columnDefinition) { + if (isNullable(columnDefinition)) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx index 2c0907f53f..f6ec3a6607 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx @@ -37,7 +37,7 @@ const StyledColumnHeaderCell = styled.th<{ `; }}; ${({ isResizing, theme }) => { - if (isResizing) { + if (isResizing === true) { return `&:after { background-color: ${theme.color.blue}; bottom: 0; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts index a880366cad..e9dc6f6d12 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell.ts @@ -11,6 +11,7 @@ import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { isNonNullable } from '~/utils/isNonNullable'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; @@ -56,7 +57,7 @@ export const useOpenRecordTableCell = () => { initFieldInputDraftValue(options?.initialValue); - if (customCellHotkeyScope) { + if (isNonNullable(customCellHotkeyScope)) { setHotkeyScope( customCellHotkeyScope.scope, customCellHotkeyScope.customScopes, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleObjectRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleObjectRecordSelect.tsx index 05aaf85f56..df90df1b82 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleObjectRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleObjectRecordSelect.tsx @@ -19,6 +19,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; +import { isNonNullable } from '~/utils/isNonNullable'; export const StyledSelectableItem = styled(SelectableItem)` height: 100%; @@ -152,7 +153,7 @@ export const MultipleObjectRecordSelect = ({ (entity) => entity.record.id === recordId, ); - if (correspondingRecordForSelect) { + if (isNonNullable(correspondingRecordForSelect)) { handleSelectChange( correspondingRecordForSelect, !recordIsSelected, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx index e0b8d30d96..4fefd10790 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx @@ -6,6 +6,7 @@ import { } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { isNonNullable } from '~/utils/isNonNullable'; export type SingleEntitySelectProps = { disableBackgroundBlur?: boolean; @@ -37,7 +38,7 @@ export const SingleEntitySelect = ({ event.target instanceof HTMLInputElement && event.target.tagName === 'INPUT' ); - if (weAreNotInAnHTMLInput && onCancel) { + if (weAreNotInAnHTMLInput && isNonNullable(onCancel)) { onCancel(); } }, 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 8273f15f81..5de44471a4 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 @@ -76,7 +76,7 @@ export const SingleEntitySelectMenuItems = ({ selectableItemIdArray={selectableItemIds} hotkeyScope={RelationPickerHotkeyScope.RelationPicker} onEnter={(itemId) => { - if (showCreateButton) { + if (showCreateButton === true) { onCreate?.(); } else { const entity = entitiesInDropdown.findIndex( diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts index 0242a92025..b8bca5a4cb 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts @@ -1,3 +1,5 @@ +import { isNonEmptyString } from '@sniptt/guards'; + import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter'; @@ -23,10 +25,10 @@ export const useSearchFilterPerMetadataItem = ({ let searchFilter: ObjectRecordQueryFilter = {}; - if (labelIdentifierFieldMetadataItem) { + if (isNonNullable(labelIdentifierFieldMetadataItem)) { switch (labelIdentifierFieldMetadataItem.type) { case FieldMetadataType.FullName: { - if (searchFilterValue) { + if (isNonEmptyString(searchFilterValue)) { const fullNameFilter = makeOrFilterVariables([ { [labelIdentifierFieldMetadataItem.name]: { @@ -44,14 +46,14 @@ export const useSearchFilterPerMetadataItem = ({ }, ]); - if (fullNameFilter) { + if (isNonNullable(fullNameFilter)) { searchFilter = fullNameFilter; } } break; } default: { - if (searchFilterValue) { + if (isNonEmptyString(searchFilterValue)) { searchFilter = { [labelIdentifierFieldMetadataItem.name]: { ilike: `%${searchFilterValue}%`, diff --git a/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts b/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts index ed72f8409a..fb93d44cc7 100644 --- a/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts +++ b/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts @@ -56,7 +56,7 @@ export const useRecordsForSelect = ({ fieldNames.map((fieldName) => { const [parentFieldName, subFieldName] = fieldName.split('.'); - if (subFieldName) { + if (isNonEmptyString(subFieldName)) { // Composite field return { [parentFieldName]: { diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/useSpreadsheetRecordImport.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/useSpreadsheetRecordImport.ts index e2cc05e27c..6a5787f67b 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/useSpreadsheetRecordImport.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/useSpreadsheetRecordImport.ts @@ -1,3 +1,5 @@ +import { isNonEmptyString } from '@sniptt/guards'; + import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; import { getSpreadSheetValidation } from '@/object-record/spreadsheet-import/util/getSpreadSheetValidation'; @@ -7,6 +9,7 @@ import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; const firstName = 'Firstname'; const lastName = 'Lastname'; @@ -128,14 +131,19 @@ export const useSpreadsheetRecordImport = (objectNameSingular: string) => { } break; case FieldMetadataType.Relation: - if (value) { + if ( + isNonNullable(value) && + (isNonEmptyString(value) || value !== false) + ) { fieldMapping[field.name + 'Id'] = value; } break; case FieldMetadataType.FullName: if ( - record[`${firstName} (${field.name})`] || - record[`${lastName} (${field.name})`] + isNonNullable( + record[`${firstName} (${field.name})`] || + record[`${lastName} (${field.name})`], + ) ) { fieldMapping[field.name] = { firstName: record[`${firstName} (${field.name})`] || '', diff --git a/packages/twenty-front/src/modules/object-record/utils/generateDeleteOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/utils/generateDeleteOneRecordMutation.ts index ad0a2840bf..5b332340a5 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateDeleteOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateDeleteOneRecordMutation.ts @@ -2,6 +2,7 @@ import { gql } from '@apollo/client'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; export const getDeleteOneRecordMutationResponseField = ( @@ -13,7 +14,7 @@ export const generateDeleteOneRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - if (!objectMetadataItem) { + if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; } diff --git a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts index 316c092cb1..cfbd511e46 100644 --- a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts +++ b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts @@ -59,7 +59,7 @@ export const useFilteredSearchEntityQuery = ({ fieldNames.map((fieldName) => { const [parentFieldName, subFieldName] = fieldName.split('.'); - if (subFieldName) { + if (isNonEmptyString(subFieldName)) { // Composite field return { [parentFieldName]: { diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListCard.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListCard.tsx index 53d84310dd..0d27482760 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListCard.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListCard.tsx @@ -55,7 +55,7 @@ export const SettingsAccountsListCard = < const theme = useTheme(); const navigate = useNavigate(); - if (isLoading) return ; + if (isLoading === true) return ; if (!accounts.length) return ; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistInput.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistInput.stories.tsx index 7a7a85aa39..b6bfdb97a2 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistInput.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistInput.stories.tsx @@ -7,7 +7,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; const updateBlockedEmailListJestFn = fn(); const ClearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { updateBlockedEmailListJestFn.mockClear(); } return ; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTable.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTable.stories.tsx index 1d3f718d36..076d9e5293 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTable.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTable.stories.tsx @@ -9,7 +9,7 @@ import { formatToHumanReadableDate } from '~/utils'; const handleBlockedEmailRemoveJestFn = fn(); const ClearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { handleBlockedEmailRemoveJestFn.mockClear(); } return ; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTableRow.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTableRow.stories.tsx index 5b8d42b18f..6742eb9122 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTableRow.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsEmailsBlocklistTableRow.stories.tsx @@ -9,7 +9,7 @@ import { formatToHumanReadableDate } from '~/utils'; const onRemoveJestFn = fn(); const ClearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { onRemoveJestFn.mockClear(); } return ; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx index e82e9a0ebb..0a5bfde6d3 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx @@ -9,7 +9,7 @@ const handleActivateMockFunction = fn(); const handleEraseMockFunction = fn(); const ClearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks) { + if (context.parameters.clearMocks === true) { handleActivateMockFunction.mockClear(); handleEraseMockFunction.mockClear(); } diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/getFieldDefaultPreviewValue.ts b/packages/twenty-front/src/modules/settings/data-model/utils/getFieldDefaultPreviewValue.ts index efb6483952..c52cd66c00 100644 --- a/packages/twenty-front/src/modules/settings/data-model/utils/getFieldDefaultPreviewValue.ts +++ b/packages/twenty-front/src/modules/settings/data-model/utils/getFieldDefaultPreviewValue.ts @@ -5,6 +5,7 @@ import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifie import { SettingsObjectFieldSelectFormValues } from '@/settings/data-model/components/SettingsObjectFieldSelectForm'; import { SETTINGS_FIELD_METADATA_TYPES } from '@/settings/data-model/constants/SettingsFieldMetadataTypes'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; export const getFieldDefaultPreviewValue = ({ fieldMetadataItem, @@ -21,14 +22,17 @@ export const getFieldDefaultPreviewValue = ({ selectOptions?: SettingsObjectFieldSelectFormValues; }) => { // Select field - if (fieldMetadataItem.type === FieldMetadataType.Select && selectOptions) { + if ( + fieldMetadataItem.type === FieldMetadataType.Select && + isNonNullable(selectOptions) + ) { return selectOptions.find(({ isDefault }) => isDefault) || selectOptions[0]; } // Relation field if ( fieldMetadataItem.type === FieldMetadataType.Relation && - relationObjectMetadataItem + isNonNullable(relationObjectMetadataItem) ) { const relationLabelIdentifierFieldMetadataItem = getLabelIdentifierFieldMetadataItem(relationObjectMetadataItem); diff --git a/packages/twenty-front/src/modules/settings/developers/utils/format-expiration.ts b/packages/twenty-front/src/modules/settings/developers/utils/format-expiration.ts index a980448155..06c19a79e4 100644 --- a/packages/twenty-front/src/modules/settings/developers/utils/format-expiration.ts +++ b/packages/twenty-front/src/modules/settings/developers/utils/format-expiration.ts @@ -1,3 +1,5 @@ +import { isNonEmptyString } from '@sniptt/guards'; + import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem'; import { ApiKey } from '@/settings/developers/types/api-key/ApiKey'; import { beautifyDateDiff } from '~/utils/date-utils'; @@ -7,7 +9,7 @@ export const formatExpiration = ( withExpiresMention: boolean = false, short: boolean = true, ) => { - if (expiresAt) { + if (isNonEmptyString(expiresAt)) { const dateDiff = beautifyDateDiff(expiresAt, undefined, short); if (dateDiff.includes('-')) { return 'Expired'; diff --git a/packages/twenty-front/src/modules/settings/profile/components/ChangePassword.tsx b/packages/twenty-front/src/modules/settings/profile/components/ChangePassword.tsx index 9548704d4c..ee60971528 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/ChangePassword.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/ChangePassword.tsx @@ -27,7 +27,7 @@ export const ChangePassword = () => { email: currentUser.email, }, }); - if (data?.emailPasswordResetLink?.success) { + if (data?.emailPasswordResetLink?.success === true) { enqueueSnackBar('Password reset link has been sent to the email', { variant: 'success', }); diff --git a/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx b/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx index 0486b51bb3..7db7cd24b5 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx @@ -47,12 +47,9 @@ export const NameFields = ({ // TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com) const debouncedUpdate = debounce(async () => { - if (onFirstNameUpdate) { - onFirstNameUpdate(firstName); - } - if (onLastNameUpdate) { - onLastNameUpdate(lastName); - } + onFirstNameUpdate?.(firstName); + onLastNameUpdate?.(lastName); + try { if (!currentWorkspaceMember?.id) { throw new Error('User is not logged in'); diff --git a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index 59856007dd..751422c6f7 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -7,6 +7,8 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { ImageInput } from '@/ui/input/components/ImageInput'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; import { useUploadProfilePictureMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; export const ProfilePictureUploader = () => { const [uploadPicture, { loading: isUploading }] = @@ -25,7 +27,7 @@ export const ProfilePictureUploader = () => { }); const handleUpload = async (file: File) => { - if (!file) { + if (isNullable(file)) { return; } @@ -72,7 +74,7 @@ export const ProfilePictureUploader = () => { }; const handleAbort = async () => { - if (uploadController) { + if (isNonNullable(uploadController)) { uploadController.abort(); setUploadController(null); } 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 d0f33e002b..f7337f9847 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx @@ -6,6 +6,8 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { TextInput } from '@/ui/input/components/TextInput'; import { useUpdateWorkspaceMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { logError } from '~/utils/logError'; const StyledComboInputContainer = styled.div` @@ -37,7 +39,7 @@ export const NameField = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedUpdate = useCallback( debounce(async (name: string) => { - if (onNameUpdate) { + if (isNonNullable(onNameUpdate)) { onNameUpdate(displayName); } if (!autoSave || !name) { @@ -52,7 +54,7 @@ export const NameField = ({ }, }); - if (errors || !data?.updateWorkspace) { + if (isNonNullable(errors) || isNullable(data?.updateWorkspace)) { throw errors; } } catch (error) { diff --git a/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx b/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx index 34eb5a5fa1..7f004a5999 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx @@ -7,6 +7,7 @@ import { useUpdateWorkspaceMutation, useUploadWorkspaceLogoMutation, } from '~/generated/graphql'; +import { isNullable } from '~/utils/isNullable'; export const WorkspaceLogoUploader = () => { const [uploadLogo] = useUploadWorkspaceLogoMutation(); @@ -16,7 +17,7 @@ export const WorkspaceLogoUploader = () => { ); const onUpload = async (file: File) => { - if (!file) { + if (isNullable(file)) { return; } if (!currentWorkspace?.id) { diff --git a/packages/twenty-front/src/modules/spreadsheet-import/components/Providers.tsx b/packages/twenty-front/src/modules/spreadsheet-import/components/Providers.tsx index 2a06b67fc2..e1cc3c1fa7 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/components/Providers.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/components/Providers.tsx @@ -1,6 +1,7 @@ import { createContext } from 'react'; import { SpreadsheetOptions } from '@/spreadsheet-import/types'; +import { isNullable } from '~/utils/isNullable'; export const RsiContext = createContext({} as any); @@ -13,7 +14,7 @@ export const Providers = ({ children, values, }: ProvidersProps) => { - if (!values.fields) { + if (isNullable(values.fields)) { throw new Error('Fields must be provided to spreadsheet-import'); } diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadFlow.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadFlow.tsx index e9bd1f8fd0..7e6c07e92d 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadFlow.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadFlow.tsx @@ -95,7 +95,7 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => { const isSingleSheet = workbook.SheetNames.length === 1; if (isSingleSheet) { if ( - maxRecords && + maxRecords > 0 && exceedsMaxRecords( workbook.Sheets[workbook.SheetNames[0]], maxRecords, @@ -147,7 +147,7 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => { sheetNames={state.workbook.SheetNames} onContinue={async (sheetName) => { if ( - maxRecords && + maxRecords > 0 && exceedsMaxRecords(state.workbook.Sheets[sheetName], maxRecords) ) { errorToast( diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx index dc435f88e7..9499b91b36 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx @@ -14,6 +14,7 @@ import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogMa import { Button } from '@/ui/input/button/components/Button'; import { Toggle } from '@/ui/input/components/Toggle'; import { Modal } from '@/ui/layout/modal/components/Modal'; +import { isNonNullable } from '~/utils/isNonNullable'; import { generateColumns } from './components/columns'; import { Meta } from './types'; @@ -94,7 +95,7 @@ export const ValidationStep = ({ ); const deleteSelectedRows = () => { - if (selectedRows.size) { + if (selectedRows.size > 0) { const newData = data.filter((value) => !selectedRows.has(value.__index)); updateData(newData); setSelectedRows(new Set()); @@ -129,7 +130,7 @@ export const ValidationStep = ({ const tableData = useMemo(() => { if (filterByErrors) { return data.filter((value) => { - if (value?.__errors) { + if (isNonNullable(value?.__errors)) { return Object.values(value.__errors)?.filter( (err) => err.level === 'error', ).length; @@ -146,7 +147,7 @@ export const ValidationStep = ({ const calculatedData = data.reduce( (acc, value) => { const { __index, __errors, ...values } = value; - if (__errors) { + if (isNonNullable(__errors)) { for (const key in __errors) { if (__errors[key].level === 'error') { acc.invalidData.push(values as unknown as Data); @@ -165,7 +166,7 @@ export const ValidationStep = ({ }; const onContinue = () => { const invalidData = data.find((value) => { - if (value?.__errors) { + if (isNonNullable(value?.__errors)) { return !!Object.values(value.__errors)?.filter( (err) => err.level === 'error', ).length; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx index 81b0edac40..19ebedbb36 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx @@ -9,6 +9,7 @@ import { AppTooltip } from '@/ui/display/tooltip/AppTooltip'; import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; import { TextInput } from '@/ui/input/components/TextInput'; import { Toggle } from '@/ui/input/components/Toggle'; +import { isNonNullable } from '~/utils/isNonNullable'; import { Meta } from '../types'; @@ -207,7 +208,7 @@ export const generateColumns = ( ); } - if (row.__errors?.[columnKey]) { + if (isNonNullable(row.__errors?.[columnKey])) { return ( <> {component} diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/dataMutations.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/dataMutations.ts index 55c0b473e5..0fa3936cb4 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/dataMutations.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/dataMutations.ts @@ -1,3 +1,4 @@ +import { isNonEmptyString } from '@sniptt/guards'; import { v4 } from 'uuid'; import { @@ -11,6 +12,8 @@ import { RowHook, TableHook, } from '@/spreadsheet-import/types'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; export const addErrorsAndRunHooks = ( data: (Data & Partial)[], @@ -27,11 +30,11 @@ export const addErrorsAndRunHooks = ( }; }; - if (tableHook) { + if (isNonNullable(tableHook)) { data = tableHook(data, addHookError); } - if (rowHook) { + if (isNonNullable(rowHook)) { data = data.map((value, index) => rowHook(value, (...props) => addHookError(index, ...props), data), ); @@ -47,7 +50,10 @@ export const addErrorsAndRunHooks = ( const duplicates = new Set(); // Set of items used multiple times values.forEach((value) => { - if (validation.allowEmpty && !value) { + if ( + validation.allowEmpty === true && + (isNullable(value) || value === '' || !value) + ) { // If allowEmpty is set, we will not validate falsy fields such as undefined or empty string. return; } @@ -95,7 +101,7 @@ export const addErrorsAndRunHooks = ( data.forEach((entry, index) => { const value = entry[field.key]?.toString(); - if (value && !value.match(regex)) { + if (isNonEmptyString(value) && !value.match(regex)) { errors[index] = { ...errors[index], [field.key]: { @@ -113,7 +119,7 @@ export const addErrorsAndRunHooks = ( data.forEach((entry, index) => { const value = entry[field.key]?.toString(); - if (value && !validation.isValid(value)) { + if (isNonEmptyString(value) && !validation.isValid(value)) { errors[index] = { ...errors[index], [field.key]: { @@ -136,10 +142,10 @@ export const addErrorsAndRunHooks = ( } const newValue = value as Data & Meta; - if (errors[index]) { + if (isNonNullable(errors[index])) { return { ...newValue, __errors: errors[index] }; } - if (!errors[index] && value?.__errors) { + if (isNullable(errors[index]) && isNonNullable(value?.__errors)) { return { ...newValue, __errors: null }; } return newValue; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/getMatchedColumns.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/getMatchedColumns.ts index 4f95e2d5ef..4dda4a62d9 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/getMatchedColumns.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/getMatchedColumns.ts @@ -6,6 +6,7 @@ import { MatchColumnsStepProps, } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep'; import { Field, Fields } from '@/spreadsheet-import/types'; +import { isNonNullable } from '~/utils/isNonNullable'; import { findMatch } from './findMatch'; import { setColumn } from './setColumn'; @@ -18,7 +19,7 @@ export const getMatchedColumns = ( ) => columns.reduce[]>((arr, column) => { const autoMatch = findMatch(column.header, fields, autoMapDistance); - if (autoMatch) { + if (isNonNullable(autoMatch)) { const field = fields.find((field) => field.key === autoMatch) as Field; const duplicateIndex = arr.findIndex( (column) => 'value' in column && column.value === field.key, diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeCheckboxValue.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeCheckboxValue.ts index f8ada87f45..cfb930f429 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeCheckboxValue.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeCheckboxValue.ts @@ -1,3 +1,5 @@ +import { isNonEmptyString } from '@sniptt/guards'; + const booleanWhitelist: Record = { yes: true, no: false, @@ -6,7 +8,7 @@ const booleanWhitelist: Record = { }; export const normalizeCheckboxValue = (value: string | undefined): boolean => { - if (value && value.toLowerCase() in booleanWhitelist) { + if (isNonEmptyString(value) && value.toLowerCase() in booleanWhitelist) { return booleanWhitelist[value.toLowerCase()]; } return false; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts index 309108f04a..0782ab69a8 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts @@ -24,7 +24,7 @@ export const normalizeTableData = ( if ( 'booleanMatches' in field.fieldType && - Object.keys(field.fieldType).length + Object.keys(field.fieldType).length > 0 ) { const booleanMatchKey = Object.keys( field.fieldType.booleanMatches || [], diff --git a/packages/twenty-front/src/modules/support/components/SupportChat.tsx b/packages/twenty-front/src/modules/support/components/SupportChat.tsx index a9993ab113..7a0818c7bb 100644 --- a/packages/twenty-front/src/modules/support/components/SupportChat.tsx +++ b/packages/twenty-front/src/modules/support/components/SupportChat.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; @@ -9,6 +10,7 @@ import { IconHelpCircle } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { User } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledButtonContainer = styled.div` display: flex; @@ -24,9 +26,9 @@ const insertScript = ({ onLoad?: (...args: any[]) => void; }) => { const script = document.createElement('script'); - if (src) script.src = src; - if (innerHTML) script.innerHTML = innerHTML; - if (onLoad) script.onload = onLoad; + if (isNonEmptyString(src)) script.src = src; + if (isNonEmptyString(innerHTML)) script.innerHTML = innerHTML; + if (isNonNullable(onLoad)) script.onload = onLoad; document.body.appendChild(script); }; @@ -70,9 +72,9 @@ export const SupportChat = () => { useEffect(() => { if ( supportChat?.supportDriver === 'front' && - supportChat.supportFrontChatId && - currentUser?.email && - currentWorkspaceMember && + isNonEmptyString(supportChat.supportFrontChatId) && + isNonEmptyString(currentUser?.email) && + isNonNullable(currentWorkspaceMember) && !isFrontChatLoaded ) { configureFront( diff --git a/packages/twenty-front/src/modules/ui/display/chip/components/EntityChip.tsx b/packages/twenty-front/src/modules/ui/display/chip/components/EntityChip.tsx index 9d64cba075..017921da17 100644 --- a/packages/twenty-front/src/modules/ui/display/chip/components/EntityChip.tsx +++ b/packages/twenty-front/src/modules/ui/display/chip/components/EntityChip.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { useNavigate } from 'react-router-dom'; import { useTheme } from '@emotion/react'; +import { isNonEmptyString } from '@sniptt/guards'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { Avatar, AvatarType } from '@/users/components/Avatar'; @@ -41,7 +42,7 @@ export const EntityChip = ({ const theme = useTheme(); const handleLinkClick = (event: React.MouseEvent) => { - if (linkToEntity) { + if (isNonEmptyString(linkToEntity)) { event.preventDefault(); event.stopPropagation(); navigate(linkToEntity); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx index 259e15a08f..6b8547536f 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/Dialog.tsx @@ -5,6 +5,7 @@ import { Key } from 'ts-key-enum'; import { Button } from '@/ui/input/button/components/Button'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { isNonNullable } from '~/utils/isNonNullable'; import { DialogHotkeyScope } from '../types/DialogHotkeyScope'; @@ -104,7 +105,7 @@ export const Dialog = ({ event.preventDefault(); - if (confirmButton) { + if (isNonNullable(confirmButton)) { confirmButton?.onClick?.(event); closeSnackbar(); } diff --git a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx index fc73db7713..bae0aed2e5 100644 --- a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx @@ -8,6 +8,7 @@ import { ProgressBarControls, } from '@/ui/feedback/progress-bar/components/ProgressBar'; import { RGBA } from '@/ui/theme/constants/Rgba'; +import { isNonNullable } from '~/utils/isNonNullable'; import { usePausableTimeout } from '../hooks/usePausableTimeout'; @@ -131,7 +132,7 @@ export const SnackBar = ({ ); const icon = useMemo(() => { - if (iconComponent) { + if (isNonNullable(iconComponent)) { return iconComponent; } diff --git a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/hooks/usePausableTimeout.ts b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/hooks/usePausableTimeout.ts index df4de15ca1..f8c4d59adb 100644 --- a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/hooks/usePausableTimeout.ts +++ b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/hooks/usePausableTimeout.ts @@ -1,5 +1,7 @@ import { useCallback, useEffect, useRef } from 'react'; +import { isNonNullable } from '~/utils/isNonNullable'; + export const usePausableTimeout = (callback: () => void, delay: number) => { // eslint-disable-next-line @nx/workspace-no-state-useref const savedCallback = useRef<() => void>(callback); @@ -11,7 +13,7 @@ export const usePausableTimeout = (callback: () => void, delay: number) => { const timeoutId = useRef | null>(null); const tick = () => { - if (savedCallback.current) { + if (isNonNullable(savedCallback.current)) { savedCallback.current(); } }; @@ -31,7 +33,7 @@ export const usePausableTimeout = (callback: () => void, delay: number) => { if (delay !== null) { startTimeout(); return () => { - if (timeoutId.current) { + if (isNonNullable(timeoutId.current)) { clearTimeout(timeoutId.current); } }; @@ -39,7 +41,7 @@ export const usePausableTimeout = (callback: () => void, delay: number) => { }, [delay, startTimeout]); const pauseTimeout = () => { - if (timeoutId.current) { + if (isNonNullable(timeoutId.current)) { clearTimeout(timeoutId.current); } const elapsedTime = Date.now() - startTime.current; diff --git a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx index 8a72584b5a..fb751159d6 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx @@ -7,6 +7,7 @@ import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/S import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { CurrencyPickerDropdownButton } from '@/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton'; import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle'; +import { isNonNullable } from '~/utils/isNonNullable'; export const StyledInput = styled.input` margin: 0; @@ -117,7 +118,7 @@ export const CurrencyInput = ({ useEffect(() => { const currency = currencies.find(({ value }) => value === currencyCode); - if (currency) { + if (isNonNullable(currency)) { setInternalCurrency(currency); } }, [currencies, currencyCode]); 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 7525c18577..f40c6ffbb8 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 @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle'; +import { isNonNullable } from '~/utils/isNonNullable'; export type TextAreaInputProps = { disabled?: boolean; @@ -55,7 +56,7 @@ export const TextAreaInput = ({ const wrapperRef = useRef(null); useEffect(() => { - if (wrapperRef.current) { + if (isNonNullable(wrapperRef.current)) { wrapperRef.current.setSelectionRange( wrapperRef.current.value.length, wrapperRef.current.value.length, diff --git a/packages/twenty-front/src/modules/ui/input/button/components/ButtonGroup.tsx b/packages/twenty-front/src/modules/ui/input/button/components/ButtonGroup.tsx index 2be5c3aa1d..fcef963c37 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/ButtonGroup.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/ButtonGroup.tsx @@ -1,6 +1,8 @@ import React, { ReactNode } from 'react'; import styled from '@emotion/styled'; +import { isNonNullable } from '~/utils/isNonNullable'; + import { ButtonPosition, ButtonProps } from './Button'; const StyledButtonGroupContainer = styled.div` @@ -39,15 +41,15 @@ export const ButtonGroup = ({ const additionalProps: any = { position, variant, accent, size }; - if (variant) { + if (isNonNullable(variant)) { additionalProps.variant = variant; } - if (accent) { + if (isNonNullable(accent)) { additionalProps.variant = variant; } - if (size) { + if (isNonNullable(size)) { additionalProps.size = size; } diff --git a/packages/twenty-front/src/modules/ui/input/button/components/FloatingButtonGroup.tsx b/packages/twenty-front/src/modules/ui/input/button/components/FloatingButtonGroup.tsx index a194dcc22f..49330fc04a 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/FloatingButtonGroup.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/FloatingButtonGroup.tsx @@ -1,6 +1,8 @@ import React from 'react'; import styled from '@emotion/styled'; +import { isNonNullable } from '~/utils/isNonNullable'; + import { FloatingButtonPosition, FloatingButtonProps } from './FloatingButton'; const StyledFloatingButtonGroupContainer = styled.div` @@ -40,7 +42,7 @@ export const FloatingButtonGroup = ({ applyBlur: false, }; - if (size) { + if (isNonNullable(size)) { additionalProps.size = size; } diff --git a/packages/twenty-front/src/modules/ui/input/button/components/MainButton.tsx b/packages/twenty-front/src/modules/ui/input/button/components/MainButton.tsx index c872cef410..5bdc9ae0b9 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/MainButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/MainButton.tsx @@ -19,7 +19,7 @@ const StyledButton = styled.button< >` align-items: center; background: ${({ theme, variant, disabled }) => { - if (disabled) { + if (disabled === true) { return theme.background.secondary; } @@ -34,7 +34,7 @@ const StyledButton = styled.button< }}; border: 1px solid; border-color: ${({ theme, disabled, variant }) => { - if (disabled) { + if (disabled === true) { return theme.background.transparent.lighter; } @@ -49,14 +49,14 @@ const StyledButton = styled.button< }}; border-radius: ${({ theme }) => theme.border.radius.md}; ${({ theme, disabled }) => { - if (disabled) { + if (disabled === true) { return ''; } return `box-shadow: ${theme.boxShadow.light};`; }} color: ${({ theme, variant, disabled }) => { - if (disabled) { + if (disabled === true) { return theme.font.color.light; } diff --git a/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx b/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx index 4c6641ac76..3396f0f749 100644 --- a/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx @@ -9,6 +9,7 @@ import { IconX, } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledContainer = styled.div` display: flex; @@ -39,7 +40,7 @@ const StyledPicture = styled.button<{ withPicture: boolean }>` } ${({ theme, withPicture, disabled }) => { - if (withPicture || disabled) { + if ((withPicture || disabled) === true) { return ''; } @@ -132,10 +133,11 @@ export const ImageInput = ({ ref={hiddenFileInput} accept="image/jpeg, image/png, image/gif" // to desired specification onChange={(event) => { - if (onUpload) { - if (event.target.files) { - onUpload(event.target.files[0]); - } + if ( + isNonNullable(onUpload) && + isNonNullable(event.target.files) + ) { + onUpload(event.target.files[0]); } }} /> diff --git a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx index 894222a90d..9ba2c5efc8 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; +import { isNonNullable } from '~/utils/isNonNullable'; + export type ToggleSize = 'small' | 'medium'; type ContainerProps = { @@ -56,7 +58,7 @@ export const Toggle = ({ const handleChange = () => { setIsOn(!isOn); - if (onChange) { + if (isNonNullable(onChange)) { onChange(!isOn); } }; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx index 305ca9851d..09605d9cc4 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx @@ -6,6 +6,7 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { IconChevronDown } from '@/ui/display/icon'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { isNonNullable } from '~/utils/isNonNullable'; import { CurrencyPickerHotkeyScope } from '../types/CurrencyPickerHotkeyScope'; @@ -76,7 +77,7 @@ export const CurrencyPickerDropdownButton = ({ useEffect(() => { const currency = currencies.find(({ value }) => value === valueCode); - if (currency) { + if (isNonNullable(currency)) { setSelectedCurrency(currency); } }, [valueCode, currencies]); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx index 8f5aa8119c..598aea14eb 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx @@ -9,6 +9,7 @@ import { CountryCallingCode } from 'libphonenumber-js'; import { IconChevronDown, IconWorld } from '@/ui/display/icon'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { isNonNullable } from '~/utils/isNonNullable'; import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope'; @@ -112,7 +113,7 @@ export const CountryPickerDropdownButton = ({ useEffect(() => { const country = countries.find(({ countryCode }) => countryCode === value); - if (country) { + if (isNonNullable(country)) { setSelectedCountry(country); } }, [countries, value]); diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx index dac09885fc..a84edfc6af 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -14,6 +14,7 @@ import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { isNonNullable } from '~/utils/isNonNullable'; import { useDropdown } from '../hooks/useDropdown'; import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement'; @@ -59,11 +60,11 @@ export const Dropdown = ({ useDropdown(dropdownId); const offsetMiddlewares = []; - if (dropdownOffset.x) { + if (isNonNullable(dropdownOffset.x)) { offsetMiddlewares.push(offset({ crossAxis: dropdownOffset.x })); } - if (dropdownOffset.y) { + if (isNonNullable(dropdownOffset.y)) { offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y })); } diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts index 00f16c174f..5beb410c60 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts @@ -3,6 +3,7 @@ import { useRecoilState } from 'recoil'; import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId'; +import { isNonNullable } from '~/utils/isNonNullable'; export const useDropdown = (dropdownId?: string) => { const { @@ -35,7 +36,7 @@ export const useDropdown = (dropdownId?: string) => { const openDropdown = () => { setIsDropdownOpen(true); - if (dropdownHotkeyScope) { + if (isNonNullable(dropdownHotkeyScope)) { setHotkeyScopeAndMemorizePreviousScope( dropdownHotkeyScope.scope, dropdownHotkeyScope.customScopes, diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx index c0830dac5d..87a36b1594 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx @@ -4,6 +4,7 @@ import { useSelectableListHotKeys } from '@/ui/layout/selectable-list/hooks/inte import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { SelectableListScope } from '@/ui/layout/selectable-list/scopes/SelectableListScope'; import { arrayToChunks } from '~/utils/array/arrayToChunks'; +import { isNonNullable } from '~/utils/isNonNullable'; type SelectableListProps = { children: ReactNode; @@ -39,11 +40,11 @@ export const SelectableList = ({ ); } - if (selectableItemIdMatrix) { + if (isNonNullable(selectableItemIdMatrix)) { setSelectableItemIds(selectableItemIdMatrix); } - if (selectableItemIdArray) { + if (isNonNullable(selectableItemIdArray)) { setSelectableItemIds(arrayToChunks(selectableItemIdArray, 1)); } }, [selectableItemIdArray, selectableItemIdMatrix, setSelectableItemIds]); diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx index 1a15ef112e..91340db8d0 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx @@ -1,3 +1,4 @@ +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilCallback } from 'recoil'; import { Key } from 'ts-key-enum'; @@ -104,12 +105,12 @@ export const useSelectableListHotKeys = ( const nextId = computeNextId(direction); if (selectedItemId !== nextId) { - if (nextId) { + if (isNonEmptyString(nextId)) { set(isSelectedItemIdSelector(nextId), true); set(selectedItemIdState(), nextId); } - if (selectedItemId) { + if (isNonEmptyString(selectedItemId)) { set(isSelectedItemIdSelector(selectedItemId), false); } } @@ -144,7 +145,7 @@ export const useSelectableListHotKeys = ( selectableListOnEnterState(), ); - if (selectedItemId) { + if (isNonEmptyString(selectedItemId)) { onEnter?.(selectedItemId); } }, diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 680d5329f1..d99296f86f 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -8,6 +8,7 @@ import { beautifyExactDateTime, beautifyPastDateRelativeToNow, } from '~/utils/date-utils'; +import { isNonNullable } from '~/utils/isNonNullable'; type ShowPageSummaryCardProps = { avatarPlaceholder: string; @@ -85,7 +86,7 @@ export const ShowPageSummaryCard = ({ const inputFileRef = useRef(null); const onFileChange = (e: ChangeEvent) => { - if (e.target.files) onUploadPicture?.(e.target.files[0]); + if (isNonNullable(e.target.files)) onUploadPicture?.(e.target.files[0]); }; const handleAvatarClick = () => { diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx index 12fba7064a..dadd59ff8b 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx @@ -20,7 +20,7 @@ export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ background: ${theme.background.transparent.medium}; } `; - } else if (disabled) { + } else if (disabled === true) { return css` background: inherit; &:hover { @@ -31,7 +31,7 @@ export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ cursor: default; `; - } else if (hovered) { + } else if (hovered === true) { return css` background: ${theme.background.transparent.light}; `; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx index b35a60fbd6..bbb4302bff 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx @@ -1,12 +1,14 @@ import { useNavigate } from 'react-router-dom'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { useSetRecoilState } from 'recoil'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { isNonNullable } from '~/utils/isNonNullable'; export type NavigationDrawerItemProps = { className?: string; @@ -36,13 +38,13 @@ const StyledItem = styled.div` border: none; border-radius: ${({ theme }) => theme.border.radius.sm}; color: ${(props) => { - if (props.active) { + if (props.active === true) { return props.theme.font.color.primary; } - if (props.danger) { + if (props.danger === true) { return props.theme.color.red; } - if (props.soon) { + if (props.soon === true) { return props.theme.font.color.light; } return props.theme.font.color.secondary; @@ -146,12 +148,12 @@ export const NavigationDrawerItem = ({ setIsNavigationDrawerOpen(false); } - if (onClick) { + if (isNonNullable(onClick)) { onClick(); return; } - if (to) navigate(to); + if (isNonEmptyString(to)) navigate(to); }; return ( diff --git a/packages/twenty-front/src/modules/ui/theme/hooks/useSystemColorScheme.ts b/packages/twenty-front/src/modules/ui/theme/hooks/useSystemColorScheme.ts index ea9f02648e..f8b0c5676e 100644 --- a/packages/twenty-front/src/modules/ui/theme/hooks/useSystemColorScheme.ts +++ b/packages/twenty-front/src/modules/ui/theme/hooks/useSystemColorScheme.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; +import { isNullable } from '~/utils/isNullable'; export const useSystemColorScheme = (): ColorScheme => { const mediaQuery = useMemo( @@ -13,7 +14,7 @@ export const useSystemColorScheme = (): ColorScheme => { ); useEffect(() => { - if (!window.matchMedia) { + if (isNullable(window.matchMedia)) { return; } diff --git a/packages/twenty-front/src/modules/ui/utilities/dimensions/components/ComputeNodeDimensions.tsx b/packages/twenty-front/src/modules/ui/utilities/dimensions/components/ComputeNodeDimensions.tsx index 4e25521423..e47fc90d9f 100644 --- a/packages/twenty-front/src/modules/ui/utilities/dimensions/components/ComputeNodeDimensions.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/dimensions/components/ComputeNodeDimensions.tsx @@ -1,6 +1,8 @@ import { ReactNode, useLayoutEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; +import { isNonNullable } from '~/utils/isNonNullable'; + type ComputeNodeDimensionsProps = { children: ( dimensions: { height: number; width: number } | undefined, @@ -32,7 +34,7 @@ export const ComputeNodeDimensions = ({ return; } const resizeObserver = new ResizeObserver(() => { - if (nodeWrapperRef.current) { + if (isNonNullable(nodeWrapperRef.current)) { setNodeDimensions({ width: nodeWrapperRef.current.offsetWidth, height: nodeWrapperRef.current.offsetHeight, diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useScopedHotkeyCallback.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useScopedHotkeyCallback.ts index 6771c525c9..6e3d2d1020 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useScopedHotkeyCallback.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useScopedHotkeyCallback.ts @@ -28,6 +28,7 @@ export const useScopedHotkeyCallback = () => .getValue(); if (!currentHotkeyScopes.includes(scope)) { + // eslint-disable-next-line @nx/workspace-explicit-boolean-predicates-in-if if (DEBUG_HOTKEY_SCOPE) { logDebug( `%cI can't call hotkey (${ @@ -42,6 +43,7 @@ export const useScopedHotkeyCallback = () => return; } + // eslint-disable-next-line @nx/workspace-explicit-boolean-predicates-in-if if (DEBUG_HOTKEY_SCOPE) { logDebug( `%cI can call hotkey (${ diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSequenceScopedHotkeys.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSequenceScopedHotkeys.ts index 46152de351..6f3d795fab 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSequenceScopedHotkeys.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSequenceScopedHotkeys.ts @@ -2,6 +2,8 @@ import { Options, useHotkeys } from 'react-hotkeys-hook'; import { Keys } from 'react-hotkeys-hook/dist/types'; import { useRecoilState } from 'recoil'; +import { isNonNullable } from '~/utils/isNonNullable'; + import { pendingHotkeyState } from '../states/internal/pendingHotkeysState'; import { useScopedHotkeyCallback } from './useScopedHotkeyCallback'; @@ -55,7 +57,7 @@ export const useSequenceHotkeys = ( setPendingHotkey(null); - if (options.preventDefault) { + if (isNonNullable(options.preventDefault)) { keyboardEvent.stopImmediatePropagation(); keyboardEvent.stopPropagation(); keyboardEvent.preventDefault(); diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts index 37b9aaf842..83b12c2fc1 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts @@ -63,15 +63,15 @@ export const useSetHotkeyScope = () => const scopesToSet: string[] = []; - if (newHotkeyScope.customScopes?.commandMenu) { + if (newHotkeyScope.customScopes?.commandMenu === true) { scopesToSet.push(AppHotkeyScope.CommandMenu); } - if (newHotkeyScope?.customScopes?.goto) { + if (newHotkeyScope?.customScopes?.goto === true) { scopesToSet.push(AppHotkeyScope.Goto); } - if (newHotkeyScope?.customScopes?.keyboardShortcutMenu) { + if (newHotkeyScope?.customScopes?.keyboardShortcutMenu === true) { scopesToSet.push(AppHotkeyScope.KeyboardShortcutMenu); } diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx index 91325ecfec..08fade9a15 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { fireEvent, render, renderHook } from '@testing-library/react'; +import { isNonNullable } from '~/utils/isNonNullable'; + import { ClickOutsideMode, useListenClickOutside, @@ -46,7 +48,7 @@ describe('useListenClickOutside', () => { ); act(() => { - if (containerRef.current) { + if (isNonNullable(containerRef.current)) { fireEvent.mouseDown(containerRef.current); fireEvent.click(containerRef.current); } @@ -95,7 +97,7 @@ describe('useListenClickOutsideByClassName', () => { act(() => { const notClickableElement = container.querySelector('.will-trigger'); - if (notClickableElement) { + if (isNonNullable(notClickableElement)) { fireEvent.mouseDown(notClickableElement); fireEvent.click(notClickableElement); } @@ -122,7 +124,7 @@ describe('useListenClickOutsideByClassName', () => { act(() => { const notClickableElement = container.querySelector('.wont-trigger'); - if (notClickableElement) { + if (isNonNullable(notClickableElement)) { fireEvent.mouseDown(notClickableElement); fireEvent.click(notClickableElement); } diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideV2.test.tsx b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideV2.test.tsx index 12f2542991..928b9bbefc 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideV2.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideV2.test.tsx @@ -7,6 +7,7 @@ import { ClickOutsideMode, useListenClickOutsideV2, } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; +import { isNonNullable } from '~/utils/isNonNullable'; const containerRef = React.createRef(); const nullRef = React.createRef(); @@ -76,7 +77,7 @@ describe('useListenClickOutsideV2', () => { ); act(() => { - if (containerRef.current) { + if (isNonNullable(containerRef.current)) { fireEvent.mouseDown(containerRef.current); fireEvent.click(containerRef.current); } @@ -100,7 +101,7 @@ describe('useListenClickOutsideV2', () => { ); act(() => { - if (containerRef.current) { + if (isNonNullable(containerRef.current)) { fireEvent.mouseDown(containerRef.current); fireEvent.click(containerRef.current); } diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId.ts index 6a747291ef..6dd6b2ed5b 100644 --- a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId.ts +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId.ts @@ -1,3 +1,5 @@ +import { isNonEmptyString } from '@sniptt/guards'; + import { ScopeInternalContext } from '../types/ScopeInternalContext'; import { useScopeInternalContext } from './useScopeInternalContext'; @@ -10,9 +12,9 @@ export const useAvailableScopeIdOrThrow = ( const scopeIdFromContext = scopeInternalContext?.scopeId; - if (scopeIdFromProps) { + if (isNonEmptyString(scopeIdFromProps)) { return scopeIdFromProps; - } else if (scopeIdFromContext) { + } else if (isNonEmptyString(scopeIdFromContext)) { return scopeIdFromContext; } else { throw new Error('Scope id is not provided and cannot be found in context.'); diff --git a/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/__tests__/useListenScroll.test.tsx b/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/__tests__/useListenScroll.test.tsx index 8b5fc06578..8ac2c33c3d 100644 --- a/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/__tests__/useListenScroll.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/__tests__/useListenScroll.test.tsx @@ -1,9 +1,11 @@ import React from 'react'; +import { expect } from '@storybook/test'; import { act, fireEvent, renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilValue } from 'recoil'; import { useListenScroll } from '@/ui/utilities/scroll/hooks/useListenScroll'; import { isScrollingState } from '@/ui/utilities/scroll/states/isScrollingState'; +import { isNonNullable } from '~/utils/isNonNullable'; const containerRef = React.createRef(); @@ -38,7 +40,7 @@ describe('useListenScroll', () => { const container = document.querySelector('#container'); act(() => { - if (container) fireEvent.scroll(container); + if (isNonNullable(container)) fireEvent.scroll(container); }); expect(result.current.isScrolling).toBe(true); diff --git a/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/useScrollWrapperScopedRef.ts b/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/useScrollWrapperScopedRef.ts index 40754b8722..ee339b0a6b 100644 --- a/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/useScrollWrapperScopedRef.ts +++ b/packages/twenty-front/src/modules/ui/utilities/scroll/hooks/useScrollWrapperScopedRef.ts @@ -1,11 +1,13 @@ import { useContext } from 'react'; +import { isNullable } from '~/utils/isNullable'; + import { ScrollWrapperContext } from '../components/ScrollWrapper'; export const useScrollWrapperScopedRef = () => { const scrollWrapperRef = useContext(ScrollWrapperContext); - if (!scrollWrapperRef) + if (isNullable(scrollWrapperRef)) throw new Error( `Using a scroll ref without a ScrollWrapper : verify that you are using a ScrollWrapper if you intended to do so.`, ); diff --git a/packages/twenty-front/src/modules/users/components/Avatar.tsx b/packages/twenty-front/src/modules/users/components/Avatar.tsx index 9097c3ddce..d28064bb2f 100644 --- a/packages/twenty-front/src/modules/users/components/Avatar.tsx +++ b/packages/twenty-front/src/modules/users/components/Avatar.tsx @@ -107,7 +107,7 @@ export const Avatar = ({ const noAvatarUrl = !isNonEmptyString(avatarUrl); const [isInvalidAvatarUrl, setIsInvalidAvatarUrl] = useState(false); useEffect(() => { - if (avatarUrl) { + if (isNonEmptyString(avatarUrl)) { new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(false); diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx index 7bb02561eb..fe73117ad6 100644 --- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx @@ -7,6 +7,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; +import { isNonNullable } from '~/utils/isNonNullable'; export const UserProvider = ({ children }: React.PropsWithChildren) => { const [isLoading, setIsLoading] = useState(true); @@ -24,11 +25,11 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => { if (!queryLoading) { setIsLoading(false); } - if (queryData?.currentUser) { + if (isNonNullable(queryData?.currentUser)) { setCurrentUser(queryData.currentUser); setCurrentWorkspace(queryData.currentUser.defaultWorkspace); } - if (queryData?.currentUser?.workspaceMember) { + if (isNonNullable(queryData?.currentUser?.workspaceMember)) { const workspaceMember = queryData.currentUser.workspaceMember; setCurrentWorkspaceMember({ ...workspaceMember, diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 55537c4738..36087d150b 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -8,6 +8,7 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { EditableFilterChip } from '@/views/components/EditableFilterChip'; import { useViewBar } from '@/views/hooks/useViewBar'; import { ViewFilter } from '@/views/types/ViewFilter'; +import { isNonNullable } from '~/utils/isNonNullable'; type EditableFilterDropdownButtonProps = { viewFilterDropdownId: string; @@ -39,7 +40,7 @@ export const EditableFilterDropdownButton = ({ filterDefinition.fieldMetadataId === viewFilter.fieldMetadataId, ); - if (filterDefinition) { + if (isNonNullable(filterDefinition)) { setFilterDefinitionUsedInDropdown(filterDefinition); setSelectedOperandInDropdown(viewFilter.operand); setSelectedFilter(viewFilter); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx index 4bf984b09e..ee64965b1c 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx @@ -7,6 +7,8 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useViewBar } from '@/views/hooks/useViewBar'; import { GraphQLView } from '@/views/types/GraphQLView'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { useViewScopedStates } from '../hooks/internal/useViewScopedStates'; @@ -50,11 +52,11 @@ export const ViewBarEffect = () => { newViews[0] ?? null; - if (!currentView) return; + if (isNullable(currentView)) return; setCurrentViewId(currentView.id); - if (currentView?.viewFields) { + if (isNonNullable(currentView?.viewFields)) { loadViewFields(currentView.viewFields, currentView.id); loadViewFilters(currentView.viewFilters, currentView.id); loadViewSorts(currentView.viewSorts, currentView.id); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx index 3a2727f486..be740eef4a 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx @@ -5,6 +5,7 @@ import { useRecoilValue } from 'recoil'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; +import { isNonNullable } from '~/utils/isNonNullable'; type ViewBarFilterEffectProps = { filterDropdownId: string; @@ -31,11 +32,11 @@ export const ViewBarFilterEffect = ({ } = useFilterDropdown({ filterDropdownId }); useEffect(() => { - if (availableFilterDefinitions) { + if (isNonNullable(availableFilterDefinitions)) { setAvailableFilterDefinitions(availableFilterDefinitions); } - if (onFilterSelect) { + if (isNonNullable(onFilterSelect)) { setOnFilterSelect(() => onFilterSelect); } }, [ diff --git a/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx index c8428f3891..d44c03a3cd 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx @@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil'; import { useSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useSortDropdown'; import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; +import { isNonNullable } from '~/utils/isNonNullable'; type ViewBarSortEffectProps = { sortDropdownId: string; @@ -25,10 +26,10 @@ export const ViewBarSortEffect = ({ }); useEffect(() => { - if (availableSortDefinitions) { + if (isNonNullable(availableSortDefinitions)) { setAvailableSortDefinitions(availableSortDefinitions); } - if (onSortSelect) { + if (isNonNullable(onSortSelect)) { setOnSortSelect(() => onSortSelect); } }, [ diff --git a/packages/twenty-front/src/modules/views/hooks/internal/useFiltersFromQueryParams.ts b/packages/twenty-front/src/modules/views/hooks/internal/useFiltersFromQueryParams.ts index ba17a330ba..c2d31a2457 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/useFiltersFromQueryParams.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/useFiltersFromQueryParams.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; import { useApolloClient } from '@apollo/client'; +import { isNonEmptyString } from '@sniptt/guards'; import qs from 'qs'; import { useRecoilCallback } from 'recoil'; import z from 'zod'; @@ -15,6 +16,7 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; const filterQueryParamsSchema = z.object({ filter: z.record( @@ -69,7 +71,7 @@ export const useFiltersFromQueryParams = () => { field: fieldMetadataItem, }); - if (!filterDefinition) return null; + if (isNullable(filterDefinition)) return null; const relationObjectMetadataNameSingular = fieldMetadataItem.toRelationMetadata?.fromObjectMetadata @@ -94,8 +96,8 @@ export const useFiltersFromQueryParams = () => { const relationRecordNames = []; if ( - relationObjectMetadataNamePlural && - relationObjectMetadataItem && + isNonEmptyString(relationObjectMetadataNamePlural) && + isNonNullable(relationObjectMetadataItem) && Array.isArray(filterValueFromURL) ) { const queryResult = await apolloClient.query< diff --git a/packages/twenty-front/src/modules/views/hooks/internal/useViewSorts.ts b/packages/twenty-front/src/modules/views/hooks/internal/useViewSorts.ts index 989d93677d..cba3da86bf 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/useViewSorts.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/useViewSorts.ts @@ -8,6 +8,7 @@ import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; import { savedViewSortsScopedFamilyState } from '@/views/states/savedViewSortsScopedFamilyState'; import { ViewSort } from '@/views/types/ViewSort'; import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot'; +import { isNullable } from '~/utils/isNullable'; import { useViewScopedStates } from './useViewScopedStates'; @@ -42,7 +43,7 @@ export const useViewSorts = (viewScopeId: string) => { return; } - if (!currentViewSorts) { + if (isNullable(currentViewSorts)) { return; } if (!savedViewSortsByKey) { diff --git a/packages/twenty-front/src/modules/views/hooks/useViewBar.ts b/packages/twenty-front/src/modules/views/hooks/useViewBar.ts index 358dcfa3c0..a87bb7c62d 100644 --- a/packages/twenty-front/src/modules/views/hooks/useViewBar.ts +++ b/packages/twenty-front/src/modules/views/hooks/useViewBar.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { v4 } from 'uuid'; @@ -9,6 +10,7 @@ import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewSort } from '@/views/types/ViewSort'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { ViewScopeInternalContext } from '../scopes/scope-internal-context/ViewScopeInternalContext'; import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsScopedFamilyState'; @@ -112,7 +114,7 @@ export const useViewBar = (props?: UseViewProps) => { viewId: currentViewId, }); - if (!availableFieldDefinitions) { + if (isNullable(availableFieldDefinitions)) { return; } @@ -152,7 +154,7 @@ export const useViewBar = (props?: UseViewProps) => { viewId: currentViewId, }); - if (!availableFilterDefinitions) { + if (isNullable(availableFilterDefinitions)) { return; } @@ -267,11 +269,11 @@ export const useViewBar = (props?: UseViewProps) => { viewScopeId: scopeId, }); - if (savedViewFilters) { + if (isNonNullable(savedViewFilters)) { set(currentViewFiltersState, savedViewFilters); onViewFiltersChange?.(savedViewFilters); } - if (savedViewSorts) { + if (isNonNullable(savedViewSorts)) { set(currentViewSortsState, savedViewSorts); onViewSortsChange?.(savedViewSorts); } @@ -388,7 +390,7 @@ export const useViewBar = (props?: UseViewProps) => { return; } - if (viewEditMode === 'create' && name) { + if (viewEditMode === 'create' && isNonEmptyString(name)) { await createView(name); // Temporary to force refetch diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts index 9ebdd36f0d..0caab52ded 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts @@ -3,6 +3,7 @@ import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefin import { mapArrayToObject } from '~/utils/array/mapArrayToObject'; import { moveArrayItem } from '~/utils/array/moveArrayItem'; import { isNonNullable } from '~/utils/isNonNullable'; +import { isNullable } from '~/utils/isNullable'; import { ViewField } from '../types/ViewField'; @@ -25,11 +26,11 @@ export const mapViewFieldsToColumnDefinitions = ({ const correspondingColumnDefinition = columnDefinitionsByFieldMetadataId[viewField.fieldMetadataId]; - if (!correspondingColumnDefinition) return null; + if (isNullable(correspondingColumnDefinition)) return null; const { isLabelIdentifier } = correspondingColumnDefinition; - if (isLabelIdentifier) { + if (isLabelIdentifier === true) { labelIdentifierFieldMetadataId = correspondingColumnDefinition.fieldMetadataId; } diff --git a/packages/twenty-front/src/pages/auth/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/auth/ChooseYourPlan.tsx index 3e5e0de66b..66b0b64025 100644 --- a/packages/twenty-front/src/pages/auth/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/auth/ChooseYourPlan.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import styled from '@emotion/styled'; +import { isNonEmptyString, isNumber } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { SubTitle } from '@/auth/components/SubTitle.tsx'; @@ -17,6 +18,7 @@ import { useCheckoutSessionMutation, useGetProductPricesQuery, } from '~/generated/graphql.tsx'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledChoosePlanContainer = styled.div` display: flex; @@ -57,7 +59,7 @@ export const ChooseYourPlan = () => { const handlePlanChange = (type?: string) => { return () => { - if (type && planSelected !== type) { + if (isNonEmptyString(type) && planSelected !== type) { setPlanSelected(type); } }; @@ -73,7 +75,13 @@ export const ChooseYourPlan = () => { const monthPrice = prices.filter( (price) => price.recurringInterval === 'month', )?.[0]; - if (monthPrice && monthPrice.unitAmount && price.unitAmount) { + if ( + isNonNullable(monthPrice) && + isNumber(monthPrice.unitAmount) && + monthPrice.unitAmount > 0 && + isNumber(price.unitAmount) && + price.unitAmount > 0 + ) { return `Save $${(12 * monthPrice.unitAmount - price.unitAmount) / 100}`; } return 'Cancel anytime'; diff --git a/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx b/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx index ff605c5e3d..1f1e33719e 100644 --- a/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx +++ b/packages/twenty-front/src/pages/auth/CreateWorkspace.tsx @@ -21,6 +21,7 @@ import { MainButton } from '@/ui/input/button/components/MainButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { useActivateWorkspaceMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledContentContainer = styled.div` width: 100%; @@ -81,7 +82,7 @@ export const CreateWorkspace = () => { include: [FIND_MANY_OBJECT_METADATA_ITEMS], }); - if (result.errors) { + if (isNonNullable(result.errors)) { throw result.errors ?? new Error('Unknown error'); } diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index f134c5befd..36def739b8 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -5,6 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { zodResolver } from '@hookform/resolvers/zod'; +import { isNonEmptyString } from '@sniptt/guards'; import { motion } from 'framer-motion'; import { z } from 'zod'; @@ -112,7 +113,7 @@ export const PasswordReset = () => { } }, onCompleted: (data) => { - if (data?.validatePasswordResetToken?.email) { + if (isNonEmptyString(data?.validatePasswordResetToken?.email)) { setEmail(data.validatePasswordResetToken.email); } }, diff --git a/packages/twenty-front/src/pages/impersonate/ImpersonateEffect.tsx b/packages/twenty-front/src/pages/impersonate/ImpersonateEffect.tsx index 4e0902acfa..f4eaae8ffe 100644 --- a/packages/twenty-front/src/pages/impersonate/ImpersonateEffect.tsx +++ b/packages/twenty-front/src/pages/impersonate/ImpersonateEffect.tsx @@ -8,6 +8,7 @@ import { currentUserState } from '@/auth/states/currentUserState'; import { tokenPairState } from '@/auth/states/tokenPairState'; import { AppPath } from '@/types/AppPath'; import { useImpersonateMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; export const ImpersonateEffect = () => { const navigate = useNavigate(); @@ -29,7 +30,7 @@ export const ImpersonateEffect = () => { variables: { userId }, }); - if (impersonateResult.errors) { + if (isNonNullable(impersonateResult.errors)) { throw impersonateResult.errors; } @@ -47,7 +48,11 @@ export const ImpersonateEffect = () => { }, [userId, impersonate, setCurrentUser, setTokenPair]); useEffect(() => { - if (isLogged && currentUser?.canImpersonate && isNonEmptyString(userId)) { + if ( + isLogged && + currentUser?.canImpersonate === true && + isNonEmptyString(userId) + ) { handleImpersonate(); } else { // User is not allowed to impersonate or not logged in diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 563e76e078..4dc1fd3aa4 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -60,7 +60,7 @@ export const RecordShowPage = () => { const handleFavoriteButtonClick = async () => { if (!objectNameSingular || !record) return; - if (isFavorite && record) { + if (isFavorite && isNonNullable(record)) { deleteFavorite(correspondingFavorite.id); } else { createFavorite(record, objectNameSingular); diff --git a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx index 650788fb98..466f627f06 100644 --- a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx @@ -1,5 +1,6 @@ import React from 'react'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus.ts'; @@ -17,6 +18,7 @@ import { Button } from '@/ui/input/button/components/Button.tsx'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section.tsx'; import { useBillingPortalSessionQuery } from '~/generated/graphql.tsx'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledH1Title = styled(H1Title)` margin-bottom: 0; @@ -44,13 +46,13 @@ export const SettingsBilling = () => { onboardingStatus === OnboardingStatus.Canceled; const openBillingPortal = () => { - if (data) { + if (isNonNullable(data)) { window.location.replace(data.billingPortalSession.url); } }; const openChat = () => { - if (supportChat.supportDriver) { + if (isNonEmptyString(supportChat.supportDriver)) { window.FrontChat?.('show'); } else { window.location.href = diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 766bf5df47..9f8863f771 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; @@ -154,12 +155,12 @@ export const SettingsObjectFieldEdit = () => { try { if ( validatedFormValues.type === FieldMetadataType.Relation && - relationFieldMetadataItem?.id && + isNonEmptyString(relationFieldMetadataItem?.id) && hasRelationFormChanged ) { await editMetadataField({ icon: validatedFormValues.relation.field.icon, - id: relationFieldMetadataItem.id, + id: relationFieldMetadataItem?.id, label: validatedFormValues.relation.field.label, }); } diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index f14714862e..e1981453f7 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -28,6 +28,7 @@ import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { View } from '@/views/types/View'; import { ViewType } from '@/views/types/ViewType'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isNullable } from '~/utils/isNullable'; const StyledSettingsObjectFieldTypeSelect = styled( SettingsDataModelFieldTypeSelect, @@ -95,7 +96,7 @@ export const SettingsObjectNewFieldStep2 = () => { onCompleted: async (data: ObjectRecordConnection) => { const views = data.edges; - if (!views) return; + if (isNullable(views)) return; setObjectViews(data.edges.map(({ node }) => node)); }, @@ -111,7 +112,7 @@ export const SettingsObjectNewFieldStep2 = () => { onCompleted: async (data: ObjectRecordConnection) => { const views = data.edges; - if (!views) return; + if (isNullable(views)) return; setRelationObjectViews(data.edges.map(({ node }) => node)); }, 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 a94a27b372..07e96f0869 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 @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; +import { isNonEmptyString } from '@sniptt/guards'; import { DateTime } from 'luxon'; import { useRecoilState } from 'recoil'; @@ -25,6 +26,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer' import { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { useGenerateApiKeyTokenMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; const StyledInfo = styled.span` color: ${({ theme }) => theme.font.color.light}; @@ -101,15 +103,15 @@ export const SettingsDevelopersApiKeyDetail = () => { }; const regenerateApiKey = async () => { - if (apiKeyData?.name) { + if (isNonEmptyString(apiKeyData?.name)) { const newExpiresAt = computeNewExpirationDate( - apiKeyData.expiresAt, - apiKeyData.createdAt, + apiKeyData?.expiresAt, + apiKeyData?.createdAt, ); - const apiKey = await createIntegration(apiKeyData.name, newExpiresAt); + const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt); await deleteIntegration(false); - if (apiKey && apiKey.token) { + if (isNonEmptyString(apiKey?.token)) { setGeneratedApi(apiKey.id, apiKey.token); navigate(`/settings/developers/api-keys/${apiKey.id}`); } @@ -117,7 +119,7 @@ export const SettingsDevelopersApiKeyDetail = () => { }; useEffect(() => { - if (apiKeyData) { + if (isNonNullable(apiKeyData)) { return () => { setGeneratedApi(apiKeyId, null); }; diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index 950e3bfc45..63c595d5c0 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -18,6 +18,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer' import { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { useGenerateApiKeyTokenMutation } from '~/generated/graphql'; +import { isNonNullable } from '~/utils/isNonNullable'; export const SettingsDevelopersApiKeysNew = () => { const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation(); @@ -54,7 +55,7 @@ export const SettingsDevelopersApiKeysNew = () => { expiresAt: expiresAt, }, }); - if (tokenData.data?.generateApiKeyToken) { + if (isNonNullable(tokenData.data?.generateApiKeyToken)) { setGeneratedApi(newApiKey.id, tokenData.data.generateApiKeyToken.token); navigate(`/settings/developers/api-keys/${newApiKey.id}`); } diff --git a/packages/twenty-front/src/pages/tasks/TasksEffect.tsx b/packages/twenty-front/src/pages/tasks/TasksEffect.tsx index 7d164ddae8..588383510f 100644 --- a/packages/twenty-front/src/pages/tasks/TasksEffect.tsx +++ b/packages/twenty-front/src/pages/tasks/TasksEffect.tsx @@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isNonNullable } from '~/utils/isNonNullable'; import { tasksFilterDefinitions } from './tasks-filter-definitions'; @@ -26,7 +27,7 @@ export const TasksEffect = ({ filterDropdownId }: TasksEffectProps) => { }, [setAvailableFilterDefinitions]); useEffect(() => { - if (currentWorkspaceMember) { + if (isNonNullable(currentWorkspaceMember)) { setSelectedFilter({ fieldMetadataId: 'assigneeId', value: JSON.stringify(currentWorkspaceMember.id), diff --git a/packages/twenty-front/src/testing/mock-data/index.ts b/packages/twenty-front/src/testing/mock-data/index.ts index d6a930822f..52cfda4fc7 100644 --- a/packages/twenty-front/src/testing/mock-data/index.ts +++ b/packages/twenty-front/src/testing/mock-data/index.ts @@ -1,4 +1,10 @@ -import { isObject, isString } from '@sniptt/guards'; +import { + isArray, + isNonEmptyString, + isNumber, + isObject, + isString, +} from '@sniptt/guards'; import { GraphQLVariables } from 'msw'; import { isNonNullable } from '../../utils/isNonNullable'; @@ -24,7 +30,7 @@ const filterData = ( if (!['OR', 'AND', 'NOT'].includes(key)) { const filterElement = where[key] as StringFilter & { is?: object }; - if (filterElement.is) { + if (isNonNullable(filterElement.is)) { const nestedKey = Object.keys(filterElement.is)[0] as string; if ( item[key as keyof typeof item] && @@ -41,22 +47,22 @@ const filterData = ( ); } } - if (filterElement.equals) { + if (isNonEmptyString(filterElement.equals)) { return item[key as keyof typeof item] === filterElement.equals; } - if (filterElement.contains) { + if (isNonEmptyString(filterElement.contains)) { return (item[key as keyof typeof item] as string) .toLocaleLowerCase() .includes( filterElement.contains.replaceAll('%', '').toLocaleLowerCase(), ); } - if (filterElement.in) { + if (isArray(filterElement.in)) { const itemValue = item[key as keyof typeof item] as string; return filterElement.in.includes(itemValue); } - if (filterElement.notIn) { + if (isArray(filterElement.notIn)) { const itemValue = item[key as keyof typeof item] as string; if (filterElement.notIn.length === 0) return true; @@ -68,18 +74,18 @@ const filterData = ( }); // { OR: [{ firstName: filter }, { lastName: filter }] - if (where.OR && Array.isArray(where.OR)) { + if (isArray(where.OR)) { isMatch = isMatch || - where.OR.some((orFilter) => + where.OR.some((orFilter: any) => filterData(data, orFilter).includes(item), ); } - if (where.AND && Array.isArray(where.AND)) { + if (isArray(where.AND)) { isMatch = isMatch || - where.AND.every((andFilter) => + where.AND.every((andFilter: any) => filterData(data, andFilter).includes(item), ); } @@ -95,12 +101,12 @@ export const filterAndSortData = ( ): Array => { let filteredData = data; - if (where) { + if (isNonNullable(where)) { filteredData = filterData(data, where); } - if (Array.isArray(orderBy) && orderBy.length > 0 && orderBy[0]) { - const firstOrderBy = orderBy[0]; + if (isArray(orderBy) && orderBy.length > 0 && isNonNullable(orderBy[0])) { + const firstOrderBy = orderBy?.[0]; const key = Object.keys(firstOrderBy)[0]; @@ -122,7 +128,7 @@ export const filterAndSortData = ( }); } - if (limit) { + if (isNumber(limit) && limit > 0) { filteredData = filteredData.slice(0, limit); } diff --git a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts b/packages/twenty-front/src/utils/cast-as-integer-or-null.ts index 5cca0021de..21aee430f2 100644 --- a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts +++ b/packages/twenty-front/src/utils/cast-as-integer-or-null.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/workspace-explicit-boolean-predicates-in-if */ import { isNull, isNumber, isString } from '@sniptt/guards'; import { logError } from './logError'; diff --git a/packages/twenty-front/src/utils/checkUrlType.ts b/packages/twenty-front/src/utils/checkUrlType.ts index 25a39d1790..f438a4d6c6 100644 --- a/packages/twenty-front/src/utils/checkUrlType.ts +++ b/packages/twenty-front/src/utils/checkUrlType.ts @@ -1,5 +1,7 @@ import { LinkType } from '@/ui/navigation/link/components/SocialLink'; +import { isNonNullable } from './isNonNullable'; + export const checkUrlType = (url: string) => { if ( /^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test( @@ -8,7 +10,11 @@ export const checkUrlType = (url: string) => { ) { return LinkType.LinkedIn; } - if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i)) { + if ( + isNonNullable( + /^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i.exec(url), + ) + ) { return LinkType.Twitter; } diff --git a/packages/twenty-front/src/utils/date-utils.ts b/packages/twenty-front/src/utils/date-utils.ts index acdaedc73a..3a8be692ee 100644 --- a/packages/twenty-front/src/utils/date-utils.ts +++ b/packages/twenty-front/src/utils/date-utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/workspace-explicit-boolean-predicates-in-if */ import { isDate, isNumber, isString } from '@sniptt/guards'; import { differenceInCalendarDays, formatDistanceToNow } from 'date-fns'; import { DateTime } from 'luxon'; diff --git a/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts b/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts index e92e28540b..88d437a21f 100644 --- a/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts +++ b/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts @@ -1,5 +1,7 @@ import { LinkType } from '@/ui/navigation/link/components/SocialLink'; +import { isNonNullable } from './isNonNullable'; + type getUrlDisplayValueByUrlTypeProps = { type: LinkType; href: string; @@ -13,8 +15,8 @@ export const getDisplayValueByUrlType = ({ const matches = href.match( /(?:https?:\/\/)?(?:www.)?linkedin.com\/(?:in|company|school)\/(.*)/, ); - if (matches && matches[1]) { - return matches[1]; + if (isNonNullable(matches?.[1])) { + return matches?.[1]; } else { return 'LinkedIn'; } @@ -24,8 +26,8 @@ export const getDisplayValueByUrlType = ({ const matches = href.match( /(?:https?:\/\/)?(?:www.)?twitter.com\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/, ); - if (matches && matches[1]) { - return `@${matches[1]}`; + if (isNonNullable(matches?.[1])) { + return `@${matches?.[1]}`; } else { return '@twitter'; } diff --git a/packages/twenty-front/src/utils/isNonEmptyArray.ts b/packages/twenty-front/src/utils/isNonEmptyArray.ts index 7be89344ea..210f4d5908 100644 --- a/packages/twenty-front/src/utils/isNonEmptyArray.ts +++ b/packages/twenty-front/src/utils/isNonEmptyArray.ts @@ -1,9 +1,11 @@ +import { isNumber } from '@sniptt/guards'; + export const isNonEmptyArray = ( probableArray: T[] | readonly T[] | undefined | null, ): probableArray is NonNullable => { if ( Array.isArray(probableArray) && - probableArray.length && + isNumber(probableArray.length) && probableArray.length > 0 ) { return true; diff --git a/packages/twenty-front/src/utils/isNullable.ts b/packages/twenty-front/src/utils/isNullable.ts new file mode 100644 index 0000000000..8ba06347eb --- /dev/null +++ b/packages/twenty-front/src/utils/isNullable.ts @@ -0,0 +1,4 @@ +import { isNull, isUndefined } from '@sniptt/guards'; + +export const isNullable = (value: any): value is null | undefined => + isUndefined(value) || isNull(value); diff --git a/packages/twenty-front/src/utils/recoil-effects.ts b/packages/twenty-front/src/utils/recoil-effects.ts index 909c5e9418..c5b0968429 100644 --- a/packages/twenty-front/src/utils/recoil-effects.ts +++ b/packages/twenty-front/src/utils/recoil-effects.ts @@ -2,6 +2,8 @@ import { AtomEffect } from 'recoil'; import { cookieStorage } from '~/utils/cookie-storage'; +import { isNonNullable } from './isNonNullable'; + export const localStorageEffect = (key: string): AtomEffect => ({ setSelf, onSet }) => { @@ -21,7 +23,10 @@ export const cookieStorageEffect = (key: string): AtomEffect => ({ setSelf, onSet }) => { const savedValue = cookieStorage.getItem(key); - if (savedValue != null && JSON.parse(savedValue)['accessToken']) { + if ( + isNonNullable(savedValue) && + isNonNullable(JSON.parse(savedValue)['accessToken']) + ) { setSelf(JSON.parse(savedValue)); } diff --git a/packages/twenty-server/src/core/user/user.resolver.ts b/packages/twenty-server/src/core/user/user.resolver.ts index 431a5336af..595f6a6df6 100644 --- a/packages/twenty-server/src/core/user/user.resolver.ts +++ b/packages/twenty-server/src/core/user/user.resolver.ts @@ -7,10 +7,12 @@ import { Mutation, } from '@nestjs/graphql'; import { ForbiddenException, UseGuards } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import crypto from 'crypto'; import { FileUpload, GraphQLUpload } from 'graphql-upload'; +import { Repository } from 'typeorm'; import { SupportDriver } from 'src/integrations/environment/interfaces/support.interface'; import { FileFolder } from 'src/core/file/interfaces/file-folder.interface'; @@ -26,8 +28,6 @@ import { WorkspaceMember } from 'src/core/user/dtos/workspace-member.dto'; import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service'; import { UserService } from './services/user.service'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; const getHMACKey = (email?: string, key?: string | null) => { if (!email || !key) return null; diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index 78040c741d..95acb386de 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -6,6 +6,10 @@ import { rule as effectComponents, RULE_NAME as effectComponentsName, } from './rules/effect-components'; +import { + rule as explicitBooleanPredicatesInIf, + RULE_NAME as explicitBooleanPredicatesInIfName, +} from './rules/explicit-boolean-predicates-in-if'; import { rule as matchingStateVariable, RULE_NAME as matchingStateVariableName, @@ -69,6 +73,7 @@ module.exports = { [sortCssPropertiesAlphabeticallyName]: sortCssPropertiesAlphabetically, [styledComponentsPrefixedWithStyledName]: styledComponentsPrefixedWithStyled, + [explicitBooleanPredicatesInIfName]: explicitBooleanPredicatesInIf, [useGetLoadableAndGetValueToGetAtomsName]: useGetLoadableAndGetValueToGetAtoms, [maxConstsPerFileName]: maxConstsPerFile, diff --git a/tools/eslint-rules/rules/explicit-boolean-predicates-in-if.ts b/tools/eslint-rules/rules/explicit-boolean-predicates-in-if.ts new file mode 100644 index 0000000000..c8b156e3c9 --- /dev/null +++ b/tools/eslint-rules/rules/explicit-boolean-predicates-in-if.ts @@ -0,0 +1,43 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; + +export const RULE_NAME = 'explicit-boolean-predicates-in-if'; + +export const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Enforce explicit boolean predicates in if statements', + recommended: 'warn', + }, + fixable: 'code', + schema: [], + messages: { + nonExplicitPredicate: + 'Use an explicit boolean predicate in if statements.', + }, + }, + defaultOptions: [], + create: (context) => { + const parserServices = ESLintUtils.getParserServices(context); + const typeChecker = parserServices.program.getTypeChecker(); + + return { + IfStatement: (node: TSESTree.IfStatement) => { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.test); + const type = typeChecker.getTypeAtLocation(tsNode); + + if (typeChecker.typeToString(type) !== 'boolean') { + const { test } = node; + + context.report({ + node: test, + messageId: 'nonExplicitPredicate', + }); + } + }, + }; + }, +}); + +export default rule; diff --git a/tools/eslint-rules/rules/use-getLoadable-and-getValue-to-get-atoms.spec.ts b/tools/eslint-rules/rules/use-getLoadable-and-getValue-to-get-atoms.spec.ts index a05bb69500..40772c3813 100644 --- a/tools/eslint-rules/rules/use-getLoadable-and-getValue-to-get-atoms.spec.ts +++ b/tools/eslint-rules/rules/use-getLoadable-and-getValue-to-get-atoms.spec.ts @@ -25,20 +25,19 @@ ruleTester.run(RULE_NAME, rule, { code: 'const atoms = await snapshot.getPromise(someState);', errors: [ { - messageId: 'invalidWayToGetAtoms', + messageId: 'invalidAccessorOnSnapshot', }, ], - output: 'const atoms = snapshot.getLoadable(someState).getValue();', + output: 'const atoms = await snapshot.getLoadable(someState);', }, { code: 'const atoms = await snapshot.getPromise(someState(viewId));', errors: [ { - messageId: 'invalidWayToGetAtoms', + messageId: 'invalidAccessorOnSnapshot', }, ], - output: - 'const atoms = snapshot.getLoadable(someState(viewId)).getValue();', + output: 'const atoms = await snapshot.getLoadable(someState(viewId));', }, { code: 'const atoms = snapshot.getLoadable(someState).anotherMethod();', diff --git a/yarn.lock b/yarn.lock index 7f3172c81a..ae19e6a131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16456,6 +16456,17 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/experimental-utils@npm:^5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/experimental-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/utils": "npm:5.62.0" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: f7037977e00849cd8c03677a88b0659a4f0e0b1e0151aebb47c49c92b8e57408578142df598eac08b364623d926343c724f42494f87662e437b1c89f0b2e815b + languageName: node + linkType: hard + "@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0": version: 6.17.0 resolution: "@typescript-eslint/parser@npm:6.17.0" @@ -16667,6 +16678,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.45.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:6.15.0": version: 6.15.0 resolution: "@typescript-eslint/utils@npm:6.15.0" @@ -16701,24 +16730,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^5.45.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -44604,6 +44615,7 @@ __metadata: "@types/supertest": "npm:^2.0.11" "@types/uuid": "npm:^9.0.2" "@typescript-eslint/eslint-plugin": "npm:^6.10.0" + "@typescript-eslint/experimental-utils": "npm:^5.62.0" "@typescript-eslint/parser": "npm:^6.10.0" "@typescript-eslint/utils": "npm:^6.9.1" "@vitejs/plugin-react-swc": "npm:^3.5.0"