Migrate from ESLint to OxLint (#18443)
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ export const ActionButton = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{action.shortLabel ? (
|
||||
{isDefined(action.shortLabel) ? (
|
||||
<Button
|
||||
Icon={action.Icon}
|
||||
size="small"
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const ExportMultipleRecordsAction = () => {
|
||||
|
||||
const { closeActionMenu } = useCloseActionMenu({});
|
||||
|
||||
const exportProgress = progress
|
||||
const exportProgress = isDefined(progress)
|
||||
? {
|
||||
processedRecordCount: progress.processedRecordCount,
|
||||
totalRecordCount: progress.totalRecordCount,
|
||||
|
||||
+2
-2
@@ -24,11 +24,11 @@ export const ExportNoteActionSingleRecordAction = () => {
|
||||
try {
|
||||
parsedBody = JSON.parse(initialBody);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Failed to parse body for record ${recordId}, for rich text version 'v2'`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(initialBody);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { type CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type CalendarEventDetailsEffectProps = {
|
||||
record: CalendarEvent;
|
||||
@@ -12,7 +13,7 @@ export const CalendarEventDetailsEffect = ({
|
||||
const { upsertRecordsInStore } = useUpsertRecordsInStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!record) {
|
||||
if (!isDefined(record)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const CalendarEventParticipantsResponseStatus = ({
|
||||
<CalendarEventParticipantsResponseStatusField
|
||||
key={responseStatus}
|
||||
responseStatus={responseStatus}
|
||||
participants={groupedParticipants[responseStatus] || []}
|
||||
participants={groupedParticipants[responseStatus] ?? []}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ export const CalendarEventsCard = () => {
|
||||
// TODO: change animated placeholder
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noMatchRecord" />
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import { EmailThreadMessageSender } from '@/activities/emails/components/EmailTh
|
||||
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
|
||||
import { type EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { MessageChannelVisibility } from '~/generated/graphql';
|
||||
@@ -53,7 +54,7 @@ export const EmailThreadMessage = ({
|
||||
(participant) => participant.role !== MessageParticipantRole.FROM,
|
||||
);
|
||||
|
||||
if (!sender || receivers.length === 0) {
|
||||
if (!isDefined(sender) || receivers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -4,6 +4,8 @@ import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
|
||||
import { useOpenEmailThreadInSidePanel } from '@/side-panel/hooks/useOpenEmailThreadInSidePanel';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Avatar } from 'twenty-ui/display';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
@@ -74,8 +76,8 @@ type EmailThreadPreviewProps = {
|
||||
};
|
||||
|
||||
export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
|
||||
const { openEmailThreadInSidePanel } = useOpenEmailThreadInSidePanel();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { openEmailThreadInSidePanel } = useOpenEmailThreadInSidePanel();
|
||||
|
||||
const visibility = thread.visibility;
|
||||
|
||||
@@ -120,7 +122,7 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
{thread?.lastTwoParticipants?.[0] && (
|
||||
{isDefined(thread?.lastTwoParticipants?.[0]) && (
|
||||
<StyledAvatarWrapper>
|
||||
<Avatar
|
||||
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
|
||||
|
||||
@@ -90,7 +90,7 @@ export const EmailsCard = () => {
|
||||
if (!firstQueryLoading && !timelineThreads?.length) {
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="emptyInbox" />
|
||||
|
||||
@@ -170,7 +170,7 @@ export const AttachmentList = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{attachmentsWithFile && attachmentsWithFile.length > 0 && (
|
||||
{attachmentsWithFile.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
|
||||
@@ -75,13 +75,13 @@ export const DropZone = ({
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...getRootProps()}
|
||||
>
|
||||
{isDragActive && (
|
||||
<>
|
||||
<input
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...getInputProps()}
|
||||
/>
|
||||
<StyledUploadIconContainer>
|
||||
|
||||
@@ -70,7 +70,7 @@ export const FilesCard = () => {
|
||||
inputFileRef?.current?.click?.();
|
||||
};
|
||||
|
||||
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
||||
const isAttachmentsEmpty = attachments.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetRecord.targetObjectNameSingular,
|
||||
@@ -104,7 +104,7 @@ export const FilesCard = () => {
|
||||
/>
|
||||
) : (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noFile" />
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ describe.skip('downloadFile', () => {
|
||||
);
|
||||
|
||||
expect(link).not.toBeNull();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// oxlint-disable-next-line @typescripttypescript/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(link?.style?.display).toBe('none');
|
||||
|
||||
|
||||
+6
-6
@@ -113,18 +113,18 @@ export const useUpdateActivityTargetFromCell = ({
|
||||
);
|
||||
|
||||
if (isDefined(existingActivityTarget)) {
|
||||
activityTargetsAfterUpdate = activityTargetWithTargetRecords
|
||||
.map((activityTarget) => {
|
||||
activityTargetsAfterUpdate = activityTargetWithTargetRecords.flatMap(
|
||||
(activityTarget) => {
|
||||
if (
|
||||
activityTarget.targetObject.id === morphItem.recordId &&
|
||||
!morphItem.isSelected
|
||||
) {
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
|
||||
return activityTarget.activityTarget;
|
||||
})
|
||||
.filter(isDefined);
|
||||
return [activityTarget.activityTarget];
|
||||
},
|
||||
);
|
||||
|
||||
if (!morphItem.isSelected) {
|
||||
await deleteOneActivityTarget(
|
||||
|
||||
@@ -56,7 +56,7 @@ export const NoteList = ({
|
||||
button,
|
||||
}: NoteListProps) => (
|
||||
<>
|
||||
{notes && notes.length > 0 && (
|
||||
{notes.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
|
||||
@@ -43,7 +43,7 @@ export const NotesCard = () => {
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
|
||||
const isNotesEmpty = !notes || notes.length === 0;
|
||||
const isNotesEmpty = notes.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetRecord.targetObjectNameSingular,
|
||||
@@ -62,7 +62,7 @@ export const NotesCard = () => {
|
||||
if (isNotesEmpty) {
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noNote" />
|
||||
|
||||
@@ -72,7 +72,7 @@ export const TaskGroups = ({ targetableObject }: TaskGroupsProps) => {
|
||||
if (isTasksEmpty) {
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noTask" />
|
||||
|
||||
@@ -45,7 +45,7 @@ const StyledCount = styled.span`
|
||||
|
||||
export const TaskList = ({ title, tasks, button }: TaskListProps) => (
|
||||
<>
|
||||
{tasks && tasks.length > 0 && (
|
||||
{tasks.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
{title && (
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ export const TimelineCard = () => {
|
||||
useTimelineActivities(targetRecord);
|
||||
|
||||
const isTimelineActivitiesEmpty =
|
||||
!timelineActivities || timelineActivities.length === 0;
|
||||
timelineActivities.length === 0;
|
||||
|
||||
if (loading === true) {
|
||||
return <SkeletonLoader withSubSections />;
|
||||
@@ -61,7 +61,7 @@ export const TimelineCard = () => {
|
||||
if (isTimelineActivitiesEmpty) {
|
||||
const placeholderContent = (
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="emptyTimeline" />
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { EventFieldDiff } from '@/activities/timeline-activities/rows/main-object/components/EventFieldDiff';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
@@ -19,7 +20,7 @@ export const EventFieldDiffContainer = ({
|
||||
}: EventFieldDiffContainerProps) => {
|
||||
const fieldMetadataItem = fieldMetadataItemMap[diffKey];
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
throw new Error(
|
||||
`Cannot find field metadata item for field name ${diffKey} on object ${mainObjectMetadataItem.nameSingular}`,
|
||||
);
|
||||
|
||||
+4
-6
@@ -31,13 +31,11 @@ export const getActivityTargetObjectRecords = ({
|
||||
|
||||
const targets = activityTargets
|
||||
? activityTargets
|
||||
: activityRecord &&
|
||||
'noteTargets' in activityRecord &&
|
||||
activityRecord.noteTargets
|
||||
: 'noteTargets' in activityRecord &&
|
||||
isDefined(activityRecord.noteTargets)
|
||||
? activityRecord.noteTargets
|
||||
: activityRecord &&
|
||||
'taskTargets' in activityRecord &&
|
||||
activityRecord.taskTargets
|
||||
: 'taskTargets' in activityRecord &&
|
||||
isDefined(activityRecord.taskTargets)
|
||||
? activityRecord.taskTargets
|
||||
: [];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ShimmeringText } from '@/ai/components/ShimmeringText';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { type DataMessagePart } from 'twenty-shared/ai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronDown, IconChevronUp, IconCpu } from 'twenty-ui/display';
|
||||
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
@@ -65,7 +66,7 @@ export const RoutingStatusDisplay = ({
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const isLoading = data.state === 'loading';
|
||||
const isDebugMode = process.env.IS_DEBUG_MODE === 'true';
|
||||
const isExpandable = isDebugMode && data.state === 'routed' && data.debug;
|
||||
const isExpandable = isDebugMode && data.state === 'routed' && isDefined(data.debug);
|
||||
|
||||
if (data.state === 'error') {
|
||||
return null;
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
// eslint-disable-next-line twenty/no-state-useref
|
||||
// oxlint-disable-next-line twenty/no-state-useref
|
||||
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -108,7 +108,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
});
|
||||
|
||||
return apolloRef.current.getClient();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
setTokenPair,
|
||||
setCurrentUser,
|
||||
|
||||
+3
-3
@@ -56,10 +56,10 @@ export const triggerUpdateGroupByQueriesOptimisticEffect = ({
|
||||
const updatedGroupByConnections = cachedGroupByConnections.map(
|
||||
(groupConnection) => {
|
||||
const groupByDimensionValues =
|
||||
readField('groupByDimensionValues', groupConnection) || [];
|
||||
readField('groupByDimensionValues', groupConnection) ?? [];
|
||||
|
||||
const cachedEdges =
|
||||
readField<RecordGqlRefEdge[]>('edges', groupConnection) || [];
|
||||
readField<RecordGqlRefEdge[]>('edges', groupConnection) ?? [];
|
||||
|
||||
const cachedTotalCount = readField<number | undefined>(
|
||||
'totalCount',
|
||||
@@ -166,7 +166,7 @@ export const triggerUpdateGroupByQueriesOptimisticEffect = ({
|
||||
const dimensionKey = recordDimensionValues.join('|');
|
||||
const dimensionExists = updatedGroupByConnections.some((conn) => {
|
||||
const connDimensionValues =
|
||||
readField('groupByDimensionValues', conn) || [];
|
||||
readField('groupByDimensionValues', conn) ?? [];
|
||||
return (
|
||||
Array.isArray(connDimensionValues) &&
|
||||
connDimensionValues.join('|') === dimensionKey
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
},
|
||||
);
|
||||
|
||||
if (recordToCreateReference && !recordAlreadyInCache) {
|
||||
if (isDefined(recordToCreateReference) && !recordAlreadyInCache) {
|
||||
const cursor = encodeCursor(recordToCreate);
|
||||
|
||||
const edge = {
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ export const triggerDestroyRecordsOptimisticEffect = ({
|
||||
cachedEdges?.filter((cachedEdge) => {
|
||||
const nodeId = readField<string>('id', cachedEdge.node);
|
||||
|
||||
return nodeId && !recordIdsToDestroy.includes(nodeId);
|
||||
return isDefined(nodeId) && !recordIdsToDestroy.includes(nodeId);
|
||||
}) || [];
|
||||
|
||||
if (nextCachedEdges.length === cachedEdges?.length)
|
||||
|
||||
@@ -139,7 +139,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
attempts: {
|
||||
max: 2,
|
||||
retryIf: (error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('retryIf error from retryLink', error);
|
||||
if (this.isAuthenticationError(error)) {
|
||||
return false;
|
||||
@@ -163,14 +163,14 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
renewalPromise = renewToken(graphqlUri, getTokenPair())
|
||||
.then((tokens) => {
|
||||
if (isDefined(tokens)) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('setTokenPair from handleTokenRenewal');
|
||||
onTokenPairChange?.(tokens);
|
||||
cookieStorage.setItem('tokenPair', JSON.stringify(tokens));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log(
|
||||
'Failed to renew token, triggering unauthenticated error from handleTokenRenewal',
|
||||
);
|
||||
@@ -234,7 +234,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
});
|
||||
})
|
||||
.catch((sentryError) => {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to capture GraphQL error with Sentry:',
|
||||
sentryError,
|
||||
@@ -248,7 +248,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
onErrorCb?.(graphQLErrors);
|
||||
for (const graphQLError of graphQLErrors) {
|
||||
if (graphQLError.message === 'Unauthorized') {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('Unauthorized, triggering token renewal');
|
||||
return handleTokenRenewal(operation, forward);
|
||||
}
|
||||
@@ -262,7 +262,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
return;
|
||||
}
|
||||
case 'UNAUTHENTICATED': {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('UNAUTHENTICATED, triggering token renewal');
|
||||
return handleTokenRenewal(operation, forward);
|
||||
}
|
||||
@@ -294,7 +294,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
this.isRestOperation(operation) &&
|
||||
this.isAuthenticationError(networkError as ServerError)
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log(
|
||||
'Authentication error, triggering token renewal from errorLink',
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ export const createCaptchaRefreshLink = (
|
||||
return new ApolloLink((operation, forward) => {
|
||||
const { variables } = operation;
|
||||
|
||||
const hasCaptchaToken = variables && 'captchaToken' in variables;
|
||||
const hasCaptchaToken = variables != null && 'captchaToken' in variables;
|
||||
|
||||
return forward(operation).map((response) => {
|
||||
if (hasCaptchaToken) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { OperationType } from '@/apollo/types/operation-type';
|
||||
|
||||
const operationTypeColors = {
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
query: '#03A9F4',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
mutation: '#61A600',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
subscription: '#61A600',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
error: '#F51818',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
default: '#61A600',
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const getTokenPair = (): AuthTokenPair | undefined => {
|
||||
const stringTokenPair = cookieStorage.getItem('tokenPair');
|
||||
|
||||
if (!isDefined(stringTokenPair)) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('tokenPair is undefined');
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -40,7 +40,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) =>
|
||||
|
||||
console.groupCollapsed(...titleArgs);
|
||||
|
||||
if (variables && Object.keys(variables).length !== 0) {
|
||||
if (Object.keys(variables).length !== 0) {
|
||||
logDebug('VARIABLES', variables);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) =>
|
||||
errors.forEach((err: any) => {
|
||||
logDebug(
|
||||
`%c${err.message}`,
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
'color: #F51818; font-weight: lighter',
|
||||
);
|
||||
});
|
||||
@@ -78,7 +78,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) =>
|
||||
|
||||
logDebug('HEADERS: ', headers);
|
||||
|
||||
if (variables && Object.keys(variables).length !== 0) {
|
||||
if (Object.keys(variables).length !== 0) {
|
||||
logDebug('VARIABLES', variables);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,13 +107,13 @@ export class StreamingRestLink extends ApolloLink {
|
||||
try {
|
||||
const definition = operation.query.definitions[0];
|
||||
|
||||
if (!definition || definition.kind !== 'OperationDefinition') {
|
||||
if (!isDefined(definition) || definition.kind !== 'OperationDefinition') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!definition.selectionSet ||
|
||||
!definition.selectionSet.selections ||
|
||||
!isDefined(definition.selectionSet) ||
|
||||
!isDefined(definition.selectionSet.selections) ||
|
||||
definition.selectionSet.selections.length === 0
|
||||
) {
|
||||
return null;
|
||||
@@ -121,7 +121,7 @@ export class StreamingRestLink extends ApolloLink {
|
||||
|
||||
const selection = definition.selectionSet.selections[0];
|
||||
|
||||
if (!selection || !isDefined(selection.directives)) {
|
||||
if (!isDefined(selection) || !isDefined(selection.directives)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useInitializeQueryParamState = () => {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to parse billingCheckoutSession from URL',
|
||||
error,
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ export const TwoFactorAuthenticationSetupEffect = () => {
|
||||
handleTwoFactorAuthenticationProvisioningInitiation();
|
||||
|
||||
// Two factor authentication provisioning only needs to run once at mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <></>;
|
||||
|
||||
@@ -121,7 +121,7 @@ export const VerifyEmailEffect = () => {
|
||||
verifyEmailToken();
|
||||
|
||||
// Verify email only needs to run once at mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clientConfigApiStatus.isLoadedOnce]);
|
||||
|
||||
if (isError) {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const VerifyLoginTokenEffect = () => {
|
||||
navigate(AppPath.SignInUp);
|
||||
}
|
||||
// Verify only needs to run once at mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clientConfigLoaded]);
|
||||
|
||||
return <></>;
|
||||
|
||||
@@ -55,7 +55,7 @@ const StyledSeparator = styled.span`
|
||||
`;
|
||||
|
||||
export const FooterNote = () => {
|
||||
const isOnAWorkspace = useIsCurrentLocationOnAWorkspace();
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
const { shouldOfferBypass, shouldUseBypass, enableBypass } =
|
||||
useWorkspaceBypass();
|
||||
|
||||
+1
-1
@@ -244,7 +244,7 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
{(authProviders.google || authProviders.microsoft) && (
|
||||
<HorizontalSeparator />
|
||||
)}
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<FormProvider {...form}>
|
||||
<SignInUpWithCredentials isGlobalScope />
|
||||
</FormProvider>
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ export const SignInUpWorkspaceScopeForm = () => {
|
||||
<HorizontalSeparator />
|
||||
) : null}
|
||||
{providers.password && (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
<FormProvider {...form}>
|
||||
<SignInUpWithCredentials />
|
||||
</FormProvider>
|
||||
|
||||
+3
-3
@@ -231,7 +231,7 @@ export const SignInUpTOTPVerification = () => {
|
||||
<Trans>Paste the code below</Trans>
|
||||
</StyledTextContainer>
|
||||
<StyledMainContentContainer>
|
||||
{/* // eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* // oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<Controller
|
||||
name="otp"
|
||||
control={form.control}
|
||||
@@ -247,7 +247,7 @@ export const SignInUpTOTPVerification = () => {
|
||||
{slots.slice(0, 3).map((slot, idx) => (
|
||||
<Slot
|
||||
key={idx}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...slot}
|
||||
/>
|
||||
))}
|
||||
@@ -259,7 +259,7 @@ export const SignInUpTOTPVerification = () => {
|
||||
{slots.slice(3).map((slot, idx) => (
|
||||
<Slot
|
||||
key={idx}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...slot}
|
||||
/>
|
||||
))}
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export const SignInUpWorkspaceScopeFormEffect = () => {
|
||||
signInUpStep === SignInUpStep.Init &&
|
||||
!workspaceAuthProviders.google &&
|
||||
!workspaceAuthProviders.microsoft &&
|
||||
!workspaceAuthProviders.sso
|
||||
workspaceAuthProviders.sso.length === 0
|
||||
) {
|
||||
continueWithEmail();
|
||||
return;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ export const useWorkspaceFromInviteHash = () => {
|
||||
onCompleted: (data) => {
|
||||
if (
|
||||
isDefined(currentWorkspace) &&
|
||||
data?.findWorkspaceFromInviteHash &&
|
||||
isDefined(data?.findWorkspaceFromInviteHash) &&
|
||||
currentWorkspace.id === data.findWorkspaceFromInviteHash.id
|
||||
) {
|
||||
const workspaceDisplayName =
|
||||
|
||||
+3
-2
@@ -93,10 +93,11 @@ export const MeteredPriceSelector = ({
|
||||
);
|
||||
|
||||
const isChanged =
|
||||
selectedPriceId && selectedPriceId !== currentMeteredPrice?.stripePriceId;
|
||||
isDefined(selectedPriceId) &&
|
||||
selectedPriceId !== currentMeteredPrice?.stripePriceId;
|
||||
|
||||
const isUpgrade = () => {
|
||||
if (!isChanged || !selectedPrice || !currentMeteredPrice) return false;
|
||||
if (!isChanged || !isDefined(selectedPrice) || !isDefined(currentMeteredPrice)) return false;
|
||||
return (
|
||||
(selectedPrice.tiers as BillingPriceTiers)[0].flatAmount >
|
||||
(currentMeteredPrice.tiers as BillingPriceTiers)[0].flatAmount
|
||||
|
||||
@@ -56,7 +56,7 @@ export const FileBlock = createReactBlockSpec(
|
||||
},
|
||||
{
|
||||
render: ({ block, editor }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
// oxlint-disable-next-line react-hooks/rules-of-hooks
|
||||
const inputFileRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleUploadAttachment = async (file: File) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ interface BlockEditorProps {
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
// oxlint-disable-next-line twenty/no-hardcoded-colors
|
||||
const StyledEditor = styled.div`
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ export const parseInitialBlocknote = (
|
||||
try {
|
||||
parsedBody = JSON.parse(blocknote);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(logContext ?? `Failed to parse blocknote body`);
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(blocknote);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ export const useClientConfig = (): UseClientConfigResult => {
|
||||
magicLink: false,
|
||||
sso: clientConfig.authProviders.sso,
|
||||
});
|
||||
setAiModels(clientConfig.aiModels || []);
|
||||
setAiModels(clientConfig.aiModels ?? []);
|
||||
setIsAnalyticsEnabled(clientConfig.analyticsEnabled);
|
||||
setIsDeveloperDefaultSignInPrefilled(clientConfig.signInPrefilled);
|
||||
setIsMultiWorkspaceEnabled(clientConfig.isMultiWorkspaceEnabled);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IconArrowUpRight, type IconComponent } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
|
||||
@@ -51,8 +52,8 @@ export const CommandMenuItem = ({
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
withIconContainer={!LeftComponent}
|
||||
LeftIcon={LeftComponent ? undefined : Icon}
|
||||
withIconContainer={!isDefined(LeftComponent)}
|
||||
LeftIcon={isDefined(LeftComponent) ? undefined : Icon}
|
||||
LeftComponent={LeftComponent}
|
||||
text={label}
|
||||
contextualText={description}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const CommandMenuItemToggle = (props: CommandMenuItemToggleProps) => {
|
||||
|
||||
return (
|
||||
<MenuItemToggle
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
focused={isSelectedItemId}
|
||||
withIconContainer
|
||||
|
||||
+2
-2
@@ -158,13 +158,13 @@ describe('useSetGlobalCommandMenuContext', () => {
|
||||
const { setGlobalCommandMenuContext } =
|
||||
useSetGlobalCommandMenuContext();
|
||||
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const previousTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const previousNumberOfSelectedRecords = useAtomComponentStateValue(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID,
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
@@ -14,7 +15,7 @@ export const useContextStoreObjectMetadataItemOrThrow = (
|
||||
objectId: contextStoreCurrentObjectMetadataItemId ?? '',
|
||||
});
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error('Object metadata item is not set in context store');
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ export const useGetPublicWorkspaceDataByDomain = () => {
|
||||
if (isWorkspaceNotFoundError) {
|
||||
redirectToDefaultDomain();
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ObjectOptionsDropdownContextValue } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { type RecordBoardColumnHeaderAggregateDropdownContextValue } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
import { type RecordTableColumnAggregateFooterDropdownContextValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
@@ -22,7 +23,7 @@ export const useDropdownContextStateManagement = <
|
||||
}) => {
|
||||
const dropdownContext = useContext(context);
|
||||
|
||||
if (!dropdownContext) {
|
||||
if (!isDefined(dropdownContext)) {
|
||||
throw new Error(
|
||||
`useDropdownContextStateManagement must be used within a context provider (${context.Provider.name})`,
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const AppErrorBoundary = ({
|
||||
return scope;
|
||||
});
|
||||
} catch (sentryError) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Failed to capture exception with Sentry:', sentryError);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export const PromiseRejectionEffect = () => {
|
||||
return scope;
|
||||
});
|
||||
} catch (sentryError) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Failed to capture exception with Sentry:', sentryError);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ export const SentryInitEffect = () => {
|
||||
|
||||
setIsSentryInitialized(true);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Failed to initialize Sentry:', error);
|
||||
} finally {
|
||||
setIsSentryInitializing(false);
|
||||
@@ -82,7 +82,7 @@ export const SentryInitEffect = () => {
|
||||
});
|
||||
setIsSentryUserDefined(true);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Failed to set Sentry user:', error);
|
||||
}
|
||||
} else if (!isDefined(currentUser) && isSentryInitialized) {
|
||||
@@ -90,7 +90,7 @@ export const SentryInitEffect = () => {
|
||||
const { setUser } = await import('@sentry/react');
|
||||
setUser(null);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Failed to clear Sentry user:', error);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ export const CurrentWorkspaceMemberFavorites = ({
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
// TODO: (Drag Drop Bug) Adding bottom margin to ensure drag-to-last-position works. Need to find better solution that doesn't affect spacing.
|
||||
// Issue: Without margin, dragging to last position triggers next folder drop area
|
||||
|
||||
+2
-2
@@ -49,9 +49,9 @@ export const CurrentWorkspaceMemberFavoritesFolders = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
(!favorites || favorites.length === 0) &&
|
||||
favorites.length === 0 &&
|
||||
!isFavoriteFolderCreating &&
|
||||
(!favoritesByFolder || favoritesByFolder.length === 0)
|
||||
favoritesByFolder.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const FavoritesDroppable = ({
|
||||
>
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const FavoritesFolderContent = ({
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{favorites.map((favorite, index) => (
|
||||
|
||||
+3
-2
@@ -71,9 +71,10 @@ export const FavoriteFolderPickerEffect = ({
|
||||
const checkedFolderIds = favorites
|
||||
.filter(
|
||||
(favorite) =>
|
||||
favorite.recordId === targetId && favorite.forWorkspaceMemberId,
|
||||
favorite.recordId === targetId &&
|
||||
isDefined(favorite.forWorkspaceMemberId),
|
||||
)
|
||||
.map((favorite) => favorite.favoriteFolderId || 'no-folder');
|
||||
.map((favorite) => favorite.favoriteFolderId ?? 'no-folder');
|
||||
setFavoriteFolderPickerChecked(checkedFolderIds);
|
||||
}, [favorites, setFavoriteFolderPickerChecked, record?.id]);
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ export const useCreateFavorite = () => {
|
||||
const relevantFavorites = favoriteFolderId
|
||||
? favorites.filter((fav) => fav.favoriteFolderId === favoriteFolderId)
|
||||
: favorites.filter(
|
||||
(fav) => !fav.favoriteFolderId && fav.forWorkspaceMemberId,
|
||||
(fav) =>
|
||||
!isDefined(fav.favoriteFolderId) &&
|
||||
isDefined(fav.forWorkspaceMemberId),
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type AttachmentFileCategory } from '@/activities/files/types/AttachmentFileCategory';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useFileIconColors } from '@/file/hooks/useFileIconColors';
|
||||
import { IconMapping } from '@/file/utils/fileIconMappings';
|
||||
import { styled } from '@linaria/react';
|
||||
@@ -46,7 +47,9 @@ export const FileIcon = ({
|
||||
<StyledIconContainer
|
||||
background={iconColors[fileCategory] ?? theme.color.gray}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />}
|
||||
{isDefined(Icon) && (
|
||||
<Icon size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
||||
)}
|
||||
</StyledIconContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -50,7 +50,8 @@ export const useMentionSearch = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const searchRecords = data?.search.edges.map((edge) => edge.node) || [];
|
||||
const searchRecords =
|
||||
data?.search.edges.map((edge) => edge.node) ?? [];
|
||||
|
||||
return searchRecords.map((searchRecord) => ({
|
||||
recordId: searchRecord.recordId,
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export const IsAppMetadataReadyEffect = () => {
|
||||
metadataStoreState,
|
||||
'objectMetadataItems',
|
||||
);
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const metadataStoreViews = useAtomFamilyStateValue(
|
||||
metadataStoreState,
|
||||
'views',
|
||||
|
||||
+2
-3
@@ -50,10 +50,9 @@ export const CurrentWorkspaceMemberNavigationMenuItemFolders = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
(!navigationMenuItemsSorted || navigationMenuItemsSorted.length === 0) &&
|
||||
navigationMenuItemsSorted.length === 0 &&
|
||||
!isNavigationMenuItemFolderCreating &&
|
||||
(!userNavigationMenuItemsByFolder ||
|
||||
userNavigationMenuItemsByFolder.length === 0)
|
||||
userNavigationMenuItemsByFolder.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -258,7 +258,7 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{folder.navigationMenuItems.map((navigationMenuItem, index) => (
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export const NavigationMenuItemDroppable = ({
|
||||
>
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{children}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export const NavigationMenuItemFolderContent = ({
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{navigationMenuItems.map((navigationMenuItem, index) => (
|
||||
|
||||
+2
-2
@@ -44,9 +44,9 @@ export const WorkspaceNavigationMenuItemFolderDragClone = ({
|
||||
return (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.dragHandleProps}
|
||||
style={{
|
||||
...draggableProvided.draggableProps.style,
|
||||
|
||||
+1
-1
@@ -242,7 +242,7 @@ export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
<StyledFolderDroppableContent
|
||||
ref={provided.innerRef}
|
||||
$compact={isEditMode || navigationMenuItems.length === 0}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{navigationMenuItems.map((navigationMenuItem, index) => {
|
||||
|
||||
+4
-2
@@ -27,7 +27,8 @@ export const useCreateNavigationMenuItem = () => {
|
||||
const relevantItems = folderId
|
||||
? navigationMenuItems.filter((item) => item.folderId === folderId)
|
||||
: navigationMenuItems.filter(
|
||||
(item) => !item.folderId && item.userWorkspaceId,
|
||||
(item) =>
|
||||
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
@@ -59,7 +60,8 @@ export const useCreateNavigationMenuItem = () => {
|
||||
const relevantItems = folderId
|
||||
? navigationMenuItems.filter((item) => item.folderId === folderId)
|
||||
: navigationMenuItems.filter(
|
||||
(item) => !item.folderId && item.userWorkspaceId,
|
||||
(item) =>
|
||||
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
|
||||
+2
-2
@@ -13,9 +13,9 @@ export const computeInsertIndexAndPosition = (
|
||||
);
|
||||
const insertRef = itemsInFolder[targetIndex];
|
||||
const lastInFolder = itemsInFolder[itemsInFolder.length - 1];
|
||||
const flatIndex = insertRef
|
||||
const flatIndex = isDefined(insertRef)
|
||||
? currentDraft.indexOf(insertRef)
|
||||
: lastInFolder
|
||||
: isDefined(lastInFolder)
|
||||
? currentDraft.indexOf(lastInFolder) + 1
|
||||
: currentDraft.length;
|
||||
const prevPosition = itemsInFolder[targetIndex - 1]?.position ?? 0;
|
||||
|
||||
+1
-1
@@ -178,7 +178,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
{(provided) => (
|
||||
<StyledWorkspaceDroppableList
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{filteredItems.map((item, index) => {
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isActiveFieldMetadataItem } from '@/object-metadata/utils/isActiveFieldMetadataItem';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -9,7 +10,7 @@ export const useActiveFieldMetadataItems = ({
|
||||
}) => {
|
||||
const activeFieldMetadataItems = useMemo(
|
||||
() =>
|
||||
objectMetadataItem
|
||||
isDefined(objectMetadataItem)
|
||||
? objectMetadataItem.readableFields.filter(
|
||||
({ id, isActive, isSystem, name }) =>
|
||||
isActiveFieldMetadataItem({
|
||||
|
||||
+8
-10
@@ -1,7 +1,6 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const objectMetadataItemsBySingularNameSelector =
|
||||
createAtomFamilySelector<ObjectMetadataItem[], string[]>({
|
||||
@@ -11,14 +10,13 @@ export const objectMetadataItemsBySingularNameSelector =
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
|
||||
return objectNameSingulars
|
||||
.map(
|
||||
(objectNameSingular) =>
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
) ?? null,
|
||||
)
|
||||
.filter(isDefined);
|
||||
return objectNameSingulars.flatMap((objectNameSingular) => {
|
||||
const found = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
return found !== undefined ? [found] : [];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ export const useAggregateRecords = <T extends AggregateRecordsData>({
|
||||
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
|
||||
aggregateQuery,
|
||||
{
|
||||
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
|
||||
variables: {
|
||||
filter,
|
||||
},
|
||||
|
||||
@@ -142,26 +142,30 @@ export const useCreateManyRecords = <
|
||||
}
|
||||
});
|
||||
|
||||
const recordsCreatedInCache = recordOptimisticRecordsInput
|
||||
.map((recordToCreate) =>
|
||||
createOneRecordInCache({
|
||||
const recordsCreatedInCache = recordOptimisticRecordsInput.flatMap(
|
||||
(recordToCreate) => {
|
||||
const created = createOneRecordInCache({
|
||||
...recordToCreate,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
});
|
||||
|
||||
return created !== undefined && created !== null ? [created] : [];
|
||||
},
|
||||
);
|
||||
|
||||
if (recordsCreatedInCache.length > 0) {
|
||||
const recordNodeCreatedInCache = recordsCreatedInCache
|
||||
.map((record) =>
|
||||
getRecordNodeFromRecord({
|
||||
const recordNodeCreatedInCache = recordsCreatedInCache.flatMap(
|
||||
(record) => {
|
||||
const node = getRecordNodeFromRecord({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
record: record,
|
||||
computeReferences: false,
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
});
|
||||
|
||||
return node !== undefined && node !== null ? [node] : [];
|
||||
},
|
||||
);
|
||||
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
|
||||
@@ -162,7 +162,7 @@ export const useCreateOneRecord = <
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!recordCreatedInCache) {
|
||||
if (!isDefined(recordCreatedInCache)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
@@ -70,7 +71,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
const results = useMemo(
|
||||
() =>
|
||||
objectResults?.map((result: RecordGqlConnectionEdgesRequired) => {
|
||||
return result
|
||||
return isDefined(result)
|
||||
? (getRecordsFromRecordConnection({
|
||||
recordConnection: result,
|
||||
}) as T[])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQuery, type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
@@ -89,7 +90,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
|
||||
const { data, loading, error, fetchMore, refetch } =
|
||||
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
|
||||
variables: {
|
||||
filter: withSoftDeleteFilter,
|
||||
orderBy,
|
||||
|
||||
@@ -56,7 +56,7 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
const { data, loading, error, refetch } = useQuery<{
|
||||
[nameSingular: string]: RecordGqlNode;
|
||||
}>(findOneRecordQuery, {
|
||||
skip: !objectMetadataItem || !objectRecordId || skip || !hasReadPermission,
|
||||
skip: !isDefined(objectMetadataItem) || !objectRecordId || skip || !hasReadPermission,
|
||||
variables: { objectRecordId },
|
||||
client: apolloCoreClient,
|
||||
onCompleted: (data) => {
|
||||
|
||||
+2
-2
@@ -102,12 +102,12 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
: null;
|
||||
|
||||
const relativeDate =
|
||||
resolvedValue && typeof resolvedValue === 'object'
|
||||
isDefined(resolvedValue) && typeof resolvedValue === 'object'
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
const safePlainDateValue: string | undefined =
|
||||
resolvedValue && typeof resolvedValue === 'string'
|
||||
isDefined(resolvedValue) && typeof resolvedValue === 'string'
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export const getRelativeDateDisplayValue = (
|
||||
relativeDate: RelativeDateFilter,
|
||||
shouldDisplayTimeZoneAbbreviation?: boolean,
|
||||
) => {
|
||||
if (!relativeDate) return '';
|
||||
if (!isDefined(relativeDate)) return '';
|
||||
const { direction, amount, unit } = relativeDate;
|
||||
|
||||
const directionFormatted = capitalize(direction.toLowerCase());
|
||||
|
||||
+2
-1
@@ -1,11 +1,12 @@
|
||||
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useObjectOptionsDropdown = () => {
|
||||
const context = useContext(ObjectOptionsDropdownContext);
|
||||
|
||||
if (!context) {
|
||||
if (!isDefined(context)) {
|
||||
throw new Error('useObjectOptionsDropdown must be used within a context');
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export const useObjectOptionsForBoard = ({
|
||||
recordIndexFieldDefinitionsByKey[fieldMetadataId];
|
||||
|
||||
return {
|
||||
...(existingBoardField || availableColumnDefinition),
|
||||
...(existingBoardField ?? availableColumnDefinition),
|
||||
isVisible: false,
|
||||
};
|
||||
}),
|
||||
|
||||
+2
-2
@@ -53,9 +53,9 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
<StyledDraggableContainer
|
||||
id={`record-board-card-${columnIndex}-${rowIndex}`}
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
data-selectable-id={recordId}
|
||||
data-select-disable
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ export const RecordBoardColumnCardsContainer = ({
|
||||
return (
|
||||
<StyledColumnCardsContainer
|
||||
ref={droppableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided?.droppableProps}
|
||||
>
|
||||
{recordIndexRecordIdsByGroup.map((recordId, index) => (
|
||||
@@ -68,7 +68,7 @@ export const RecordBoardColumnCardsContainer = ({
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
></div>
|
||||
)}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDrop
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
|
||||
|
||||
@@ -36,7 +37,7 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
||||
<>
|
||||
<StyledTagContainer>
|
||||
<Tag
|
||||
text={value ? value.toString() : '-'}
|
||||
text={isDefined(value) ? value.toString() : '-'}
|
||||
color="transparent"
|
||||
weight="regular"
|
||||
/>
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ export const RecordCalendarMonthBodyDay = ({
|
||||
<Droppable droppableId={dayKey}>
|
||||
{(droppableProvided, droppableSnapshot) => (
|
||||
<StyledCardsContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided.droppableProps}
|
||||
ref={droppableProvided.innerRef}
|
||||
isDraggedOver={droppableSnapshot.isDraggingOver}
|
||||
|
||||
+2
-2
@@ -77,9 +77,9 @@ export const RecordCalendarCardDraggableContainer = ({
|
||||
<StyledDraggableContainer
|
||||
id={`record-calendar-card-${recordId}`}
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
data-selectable-id={recordId}
|
||||
>
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { type DropResult } from '@hello-pangea/dnd';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop';
|
||||
|
||||
@@ -22,7 +23,7 @@ export const useProcessBoardCardDrop = () => {
|
||||
|
||||
const processBoardCardDrop = useCallback(
|
||||
(boardCardDropResult: DropResult, selectedRecordIds: string[]) => {
|
||||
if (!selectFieldMetadataItem) return;
|
||||
if (!isDefined(selectFieldMetadataItem)) return;
|
||||
|
||||
processGroupDrop({
|
||||
groupDropResult: boardCardDropResult,
|
||||
|
||||
+5
-1
@@ -40,7 +40,11 @@ export const RecordDetailDuplicatesSection = ({
|
||||
objectRecordIds: duplicateRecordIds,
|
||||
});
|
||||
|
||||
if (!queryResults || !queryResults[0] || queryResults[0].length === 0)
|
||||
if (
|
||||
!isDefined(queryResults) ||
|
||||
!isDefined(queryResults[0]) ||
|
||||
queryResults[0].length === 0
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ export const RecordDetailRelationSectionDropdownToMany = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById';
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { CustomError } from 'twenty-shared/utils';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
import { IconForbid, IconPencil } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
@@ -56,7 +56,7 @@ export const RecordDetailRelationSectionDropdownToOne = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
|
||||
import { recordStoreMorphOneToManyValueWithObjectNameFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { CustomError, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { CustomError, isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
|
||||
export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
|
||||
recordId,
|
||||
@@ -28,7 +28,7 @@ export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
|
||||
...recordWithObjectNameSingular,
|
||||
value: Array.isArray(recordWithObjectNameSingular.value)
|
||||
? recordWithObjectNameSingular.value
|
||||
: recordWithObjectNameSingular.value
|
||||
: isDefined(recordWithObjectNameSingular.value)
|
||||
? [recordWithObjectNameSingular.value]
|
||||
: [],
|
||||
}))
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import { type VariablePickerComponent } from '@/object-record/record-field/ui/fo
|
||||
import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type FormPhoneFieldInputProps = {
|
||||
label?: string;
|
||||
@@ -39,7 +40,7 @@ export const FormPhoneFieldInput = ({
|
||||
onChange({
|
||||
primaryPhoneCountryCode: defaultValue?.primaryPhoneCountryCode ?? '',
|
||||
primaryPhoneCallingCode: defaultValue?.primaryPhoneCallingCode ?? '',
|
||||
primaryPhoneNumber: number ? `${number}` : '',
|
||||
primaryPhoneNumber: isDefined(number) ? `${number}` : '',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -8,6 +8,7 @@ import { VariableChipStandalone } from '@/object-record/record-field/ui/form-typ
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||
import { styled } from '@linaria/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -43,7 +44,6 @@ export const FormSingleRecordFieldChip = ({
|
||||
disabled,
|
||||
}: FormSingleRecordFieldChipProps) => {
|
||||
if (
|
||||
!!draftValue &&
|
||||
draftValue.type === 'variable' &&
|
||||
isStandaloneVariableString(draftValue.value)
|
||||
) {
|
||||
@@ -56,7 +56,7 @@ export const FormSingleRecordFieldChip = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!!draftValue && draftValue.type === 'static' && !!selectedRecord) {
|
||||
if (draftValue.type === 'static' && isDefined(selectedRecord)) {
|
||||
return (
|
||||
<StyledRecordChipContainer>
|
||||
<RecordChip
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ export const FormSingleRecordPicker = ({
|
||||
}
|
||||
: {
|
||||
type: 'static',
|
||||
value: defaultValue || '',
|
||||
value: (defaultValue as string | undefined) ?? '',
|
||||
};
|
||||
|
||||
if (objectNameSingulars.length === 0) {
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { useMultiSelectFieldDisplay } from '@/object-record/record-field/ui/meta
|
||||
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const MultiSelectFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition } = useMultiSelectFieldDisplay();
|
||||
@@ -15,7 +16,7 @@ export const MultiSelectFieldDisplay = () => {
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!selectedOptions) return null;
|
||||
if (!isDefined(selectedOptions)) return null;
|
||||
|
||||
return isFocused ? (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user