Chore(front): Add more typeguards (#2136)

* Chore(front): Add more typeguards

Co-authored-by: Benjamin Mayanja V <vibenjamin6@gmail.com>
Co-authored-by: KlingerMatheus <klinger.matheus@gitstart.dev>

* Remove source map generation to avoid warnings

---------

Co-authored-by: Benjamin Mayanja V <vibenjamin6@gmail.com>
Co-authored-by: KlingerMatheus <klinger.matheus@gitstart.dev>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
gitstart-twenty
2023-10-24 10:26:47 +03:00
committed by GitHub
parent 80d558559f
commit 5acafe2fc6
29 changed files with 82 additions and 87 deletions

View File

@ -1,4 +1,5 @@
REACT_APP_SERVER_BASE_URL=http://localhost:3000
GENERATE_SOURCEMAP=false
# ———————— Optional ————————
# REACT_APP_SERVER_AUTH_URL=http://localhost:3000/auth

View File

@ -15,6 +15,7 @@
"@floating-ui/react": "^0.24.3",
"@hello-pangea/dnd": "^16.2.0",
"@hookform/resolvers": "^3.1.1",
"@sniptt/guards": "^0.2.0",
"@tabler/icons-react": "^2.30.0",
"@tanstack/react-virtual": "^3.0.0-alpha.0",
"@types/node": "^16.18.4",

View File

@ -1,10 +1,11 @@
import React, { Ref, RefCallback } from 'react';
import { isFunction } from '@sniptt/guards';
export const useCombinedRefs =
<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> =>
(node: T) => {
for (const ref of refs) {
if (typeof ref === 'function') {
if (isFunction(ref)) {
ref(node);
} else if (ref !== null && ref !== undefined) {
(ref as React.MutableRefObject<T | null>).current = node;

View File

@ -1,5 +1,6 @@
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
@ -10,7 +11,6 @@ import {
} from '@/ui/input/components/AutosizeTextInput';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { Activity, useCreateCommentMutation } from '~/generated/graphql';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { Comment } from '../comment/Comment';
import { GET_ACTIVITY } from '../graphql/queries/getActivity';

View File

@ -1,9 +1,8 @@
import { isBoolean } from '@sniptt/guards';
import { FieldBooleanValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldBooleanValue = (
fieldValue: unknown,
): fieldValue is FieldBooleanValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'boolean';
): fieldValue is FieldBooleanValue => isBoolean(fieldValue);

View File

@ -1,9 +1,8 @@
import { isString } from '@sniptt/guards';
import { FieldChipValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldChipValue = (
fieldValue: unknown,
): fieldValue is FieldChipValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'string';
): fieldValue is FieldChipValue => isString(fieldValue);

View File

@ -1,8 +1,8 @@
import { isNull, isString } from '@sniptt/guards';
import { FieldDateValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldDateValue = (
fieldValue: unknown,
): fieldValue is FieldDateValue =>
fieldValue === null ||
(fieldValue !== undefined && typeof fieldValue === 'string');
): fieldValue is FieldDateValue => isNull(fieldValue) || isString(fieldValue);

View File

@ -1,9 +1,8 @@
import { isString } from '@sniptt/guards';
import { FieldEmailValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldEmailValue = (
fieldValue: unknown,
): fieldValue is FieldEmailValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'string';
): fieldValue is FieldEmailValue => isString(fieldValue);

View File

@ -1,8 +1,8 @@
import { isNull, isNumber } from '@sniptt/guards';
import { FieldMoneyValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldMoneyValue = (
fieldValue: unknown,
): fieldValue is FieldMoneyValue =>
fieldValue === null ||
(fieldValue !== undefined && typeof fieldValue === 'number');
): fieldValue is FieldMoneyValue => isNull(fieldValue) || isNumber(fieldValue);

View File

@ -1,8 +1,8 @@
import { isNull, isNumber } from '@sniptt/guards';
import { FieldNumberValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldNumberValue = (
fieldValue: unknown,
): fieldValue is FieldNumberValue =>
fieldValue === null ||
(fieldValue !== undefined && typeof fieldValue === 'number');
): fieldValue is FieldNumberValue => isNull(fieldValue) || isNumber(fieldValue);

View File

@ -1,9 +1,8 @@
import { isString } from '@sniptt/guards';
import { FieldPhoneValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldPhoneValue = (
fieldValue: unknown,
): fieldValue is FieldPhoneValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'string';
): fieldValue is FieldPhoneValue => isString(fieldValue);

View File

@ -1,9 +1,8 @@
import { isNumber } from '@sniptt/guards';
import { FieldProbabilityValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldProbabilityValue = (
fieldValue: unknown,
): fieldValue is FieldProbabilityValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'number';
): fieldValue is FieldProbabilityValue => isNumber(fieldValue);

View File

@ -1,7 +1,9 @@
import { isObject, isUndefined } from '@sniptt/guards';
import { FieldRelationValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldRelationValue = (
fieldValue: unknown,
): fieldValue is FieldRelationValue =>
fieldValue !== undefined && typeof fieldValue === 'object';
!isUndefined(fieldValue) && isObject(fieldValue);

View File

@ -1,9 +1,8 @@
import { isString } from '@sniptt/guards';
import { FieldTextValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldTextValue = (
fieldValue: unknown,
): fieldValue is FieldTextValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'string';
): fieldValue is FieldTextValue => isString(fieldValue);

View File

@ -1,9 +1,8 @@
import { isString } from '@sniptt/guards';
import { FieldURLValue } from '../FieldMetadata';
// TODO: add yup
export const isFieldURLValue = (
fieldValue: unknown,
): fieldValue is FieldURLValue =>
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'string';
): fieldValue is FieldURLValue => isString(fieldValue);

View File

@ -1,10 +1,10 @@
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';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { Chip, ChipVariant } from './Chip';

View File

@ -1,4 +1,5 @@
import { useRef } from 'react';
import { isNonEmptyString } from '@sniptt/guards';
import debounce from 'lodash.debounce';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -9,7 +10,6 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { EntityForSelect } from '../types/EntityForSelect';

View File

@ -1,4 +1,5 @@
import { useRef } from 'react';
import { isNonEmptyString } from '@sniptt/guards';
import { Key } from 'ts-key-enum';
import { IconPlus } from '@/ui/display/icon';
@ -11,7 +12,6 @@ import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuI
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { Avatar } from '@/users/components/Avatar';
import { assertNotNull } from '~/utils/assert';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { CreateButtonId, EmptyButtonId } from '../constants';
import { useEntitySelectScroll } from '../hooks/useEntitySelectScroll';

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { stringToHslColor } from '~/utils/string-to-hsl';
import { getImageAbsoluteURIOrBase64 } from '../utils/getProfilePictureAbsoluteURI';

View File

@ -1,11 +1,11 @@
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { AppPath } from '../../modules/types/AppPath';
import { isNonEmptyString } from '../../utils/isNonEmptyString';
export const VerifyEffect = () => {
const [searchParams] = useSearchParams();

View File

@ -1,5 +1,6 @@
import { useCallback, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
@ -8,7 +9,6 @@ import { tokenPairState } from '@/auth/states/tokenPairState';
import { useImpersonateMutation } from '~/generated/graphql';
import { AppPath } from '../../modules/types/AppPath';
import { isNonEmptyString } from '../../utils/isNonEmptyString';
export const ImpersonateEffect = () => {
const navigate = useNavigate();

View File

@ -1,4 +1,5 @@
import { getOperationName } from '@apollo/client/utilities';
import { isString } from '@sniptt/guards';
import { expect } from '@storybook/jest';
import { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
@ -116,7 +117,7 @@ const editRelationMocks = (
) => [
...graphqlMocks.filter((graphqlMock) => {
if (
typeof graphqlMock.info.operationName === 'string' &&
isString(graphqlMock.info.operationName) &&
[
getOperationName(UPDATE_ONE_PERSON),
getOperationName(SEARCH_COMPANY_QUERY),

View File

@ -1,5 +1,6 @@
import { ComponentProps, JSX } from 'react';
import styled from '@emotion/styled';
import { isNumber, isString } from '@sniptt/guards';
import { Decorator } from '@storybook/react';
const StyledColumnTitle = styled.h1`
@ -75,6 +76,9 @@ const emptyDimension = {
props: () => ({}),
} as CatalogDimension;
const isStringOrNumber = (term: unknown): term is string | number =>
isString(term) || isNumber(term);
export type CatalogDimension<
ComponentType extends React.ElementType = () => JSX.Element,
> = {
@ -108,30 +112,26 @@ export const CatalogDecorator: Decorator = (Story, context) => {
<StyledColumnContainer key={value4}>
<StyledColumnTitle>
{dimension4.labels?.(value4) ??
(['string', 'number'].includes(typeof value4) ? value4 : '')}
(isStringOrNumber(value4) ? value4 : '')}
</StyledColumnTitle>
{dimension3.values.map((value3: any) => (
<StyledRowsContainer key={value3}>
<StyledRowsTitle>
{dimension3.labels?.(value3) ??
(['string', 'number'].includes(typeof value3) ? value3 : '')}
(isStringOrNumber(value3) ? value3 : '')}
</StyledRowsTitle>
{dimension2.values.map((value2: any) => (
<StyledRowContainer key={value2}>
<StyledRowTitle>
{dimension2.labels?.(value2) ??
(['string', 'number'].includes(typeof value2)
? value2
: '')}
(isStringOrNumber(value2) ? value2 : '')}
</StyledRowTitle>
{dimension1.values.map((value1: any) => {
return (
<StyledCellContainer key={value1} id={value1}>
<StyledElementTitle>
{dimension1.labels?.(value1) ??
(['string', 'number'].includes(typeof value1)
? value1
: '')}
(isStringOrNumber(value1) ? value1 : '')}
</StyledElementTitle>
<StyledElementContainer
width={options?.elementContainer?.width}

View File

@ -1,3 +1,4 @@
import { isObject, isString } from '@sniptt/guards';
import { GraphQLVariables } from 'msw';
import {
@ -27,7 +28,7 @@ const filterData = <DataT>(
const nestedKey = Object.keys(filterElement.is)[0] as string;
if (
item[key as keyof typeof item] &&
typeof item[key as keyof typeof item] === 'object'
isObject(item[key as keyof typeof item])
) {
const nestedItem = item[key as keyof typeof item];
return (
@ -116,7 +117,7 @@ export const filterAndSortData = <DataT>(
const sortDirection =
firstOrderBy[key as unknown as keyof typeof firstOrderBy];
if (typeof itemAValue === 'string' && typeof itemBValue === 'string') {
if (isString(itemAValue) && isString(itemBValue)) {
return sortDirection === 'desc'
? itemBValue.localeCompare(itemAValue)
: -itemBValue.localeCompare(itemAValue);

View File

@ -1,3 +1,5 @@
import { isNull, isNumber, isString } from '@sniptt/guards';
import { logError } from './logError';
const DEBUG_MODE = false;
@ -11,13 +13,13 @@ export const canBeCastAsIntegerOrNull = (
return false;
}
if (typeof probableNumberOrNull === 'number') {
if (isNumber(probableNumberOrNull)) {
if (DEBUG_MODE) logError('typeof probableNumberOrNull === "number"');
return Number.isInteger(probableNumberOrNull);
}
if (probableNumberOrNull === null) {
if (isNull(probableNumberOrNull)) {
if (DEBUG_MODE) logError('probableNumberOrNull === null');
return true;
@ -29,7 +31,7 @@ export const canBeCastAsIntegerOrNull = (
return true;
}
if (typeof probableNumberOrNull === 'string') {
if (isString(probableNumberOrNull)) {
const stringAsNumber = +probableNumberOrNull;
if (isNaN(stringAsNumber)) {
@ -54,7 +56,7 @@ export const castAsIntegerOrNull = (
throw new Error('Cannot cast to number or null');
}
if (probableNumberOrNull === null) {
if (isNull(probableNumberOrNull)) {
return null;
}
@ -62,11 +64,11 @@ export const castAsIntegerOrNull = (
return null;
}
if (typeof probableNumberOrNull === 'number') {
if (isNumber(probableNumberOrNull)) {
return probableNumberOrNull;
}
if (typeof probableNumberOrNull === 'string') {
if (isString(probableNumberOrNull)) {
return +probableNumberOrNull;
}

View File

@ -1,3 +1,5 @@
import { isInteger, isNull, isNumber, isString } from '@sniptt/guards';
export const canBeCastAsPositiveIntegerOrNull = (
probablePositiveNumberOrNull: string | undefined | number | null,
): probablePositiveNumberOrNull is number | null => {
@ -5,14 +7,14 @@ export const canBeCastAsPositiveIntegerOrNull = (
return false;
}
if (typeof probablePositiveNumberOrNull === 'number') {
if (isNumber(probablePositiveNumberOrNull)) {
return (
Number.isInteger(probablePositiveNumberOrNull) &&
Math.sign(probablePositiveNumberOrNull) !== -1
);
}
if (probablePositiveNumberOrNull === null) {
if (isNull(probablePositiveNumberOrNull)) {
return true;
}
@ -20,14 +22,14 @@ export const canBeCastAsPositiveIntegerOrNull = (
return true;
}
if (typeof probablePositiveNumberOrNull === 'string') {
if (isString(probablePositiveNumberOrNull)) {
const stringAsNumber = +probablePositiveNumberOrNull;
if (isNaN(stringAsNumber)) {
return false;
}
if (Number.isInteger(stringAsNumber) && Math.sign(stringAsNumber) !== -1) {
if (isInteger(stringAsNumber) && Math.sign(stringAsNumber) !== -1) {
return true;
}
}
@ -52,11 +54,11 @@ export const castAsPositiveIntegerOrNull = (
return null;
}
if (typeof probablePositiveNumberOrNull === 'number') {
if (isNumber(probablePositiveNumberOrNull)) {
return probablePositiveNumberOrNull;
}
if (typeof probablePositiveNumberOrNull === 'string') {
if (isString(probablePositiveNumberOrNull)) {
return +probablePositiveNumberOrNull;
}

View File

@ -1,3 +1,4 @@
import { isDate, isNumber, isString } from '@sniptt/guards';
import { differenceInCalendarDays, formatDistanceToNow } from 'date-fns';
import { DateTime } from 'luxon';
@ -10,11 +11,11 @@ export const parseDate = (dateToParse: Date | string | number) => {
if (!dateToParse) {
throw new Error(`Invalid date passed to formatPastDate: "${dateToParse}"`);
} else if (typeof dateToParse === 'string') {
} else if (isString(dateToParse)) {
formattedDate = DateTime.fromISO(dateToParse);
} else if (dateToParse instanceof Date) {
} else if (isDate(dateToParse)) {
formattedDate = DateTime.fromJSDate(dateToParse);
} else if (typeof dateToParse === 'number') {
} else if (isNumber(dateToParse)) {
formattedDate = DateTime.fromMillis(dateToParse);
}

View File

@ -1,15 +0,0 @@
import { isDefined } from './isDefined';
export const isNonEmptyString = (
probableNonEmptyString: string | undefined | null,
): probableNonEmptyString is string => {
if (
isDefined(probableNonEmptyString) &&
typeof probableNonEmptyString === 'string' &&
probableNonEmptyString !== ''
) {
return true;
}
return false;
};

View File

@ -3922,6 +3922,11 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sniptt/guards@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@sniptt/guards/-/guards-0.2.0.tgz#6b95d259bfd77c5db2945605e033cdcfcea97eba"
integrity sha512-QNxknl5YcrxpLtP99iEvNEJRlfXtIfpkvcEWHJKD6bHUr93dsH6OZGUF6reVBddKs1ps44lndSMoDK2Dz0n6ZQ==
"@storybook/addon-actions@7.3.0", "@storybook/addon-actions@^7.0.22":
version "7.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-7.3.0.tgz#fb1663a2810918a4147edebc5c7ff056d6ec7e94"