diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx index fd24519a27fd..9096626cf3d6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx @@ -1,5 +1,8 @@ import { useContext } from 'react'; +import { JsonFieldDisplay } from '@/object-record/record-field/meta-types/display/components/JsonFieldDisplay'; +import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; + import { FieldContext } from '../contexts/FieldContext'; import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay'; import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay'; @@ -59,5 +62,7 @@ export const FieldDisplay = () => { ) : isFieldAddress(fieldDefinition) ? ( + ) : isFieldRawJson(fieldDefinition) ? ( + ) : null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx index 63ddafac26eb..ad2279e824d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx @@ -2,9 +2,11 @@ import { useContext } from 'react'; import { AddressFieldInput } from '@/object-record/record-field/meta-types/input/components/AddressFieldInput'; import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput'; +import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input/components/RawJsonFieldInput'; import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; +import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; @@ -137,6 +139,14 @@ export const FieldInput = ({ onTab={onTab} onShiftTab={onShiftTab} /> + ) : isFieldRawJson(fieldDefinition) ? ( + ) : ( <> )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts index 6757c1bc047e..de825b7dfc3b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts @@ -5,6 +5,8 @@ import { isFieldAddress } from '@/object-record/record-field/types/guards/isFiel import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue'; +import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; +import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; @@ -88,6 +90,10 @@ export const usePersistField = () => { isFieldAddress(fieldDefinition) && isFieldAddressValue(valueToPersist); + const fieldIsRawJson = + isFieldRawJson(fieldDefinition) && + isFieldRawJsonValue(valueToPersist); + if ( fieldIsRelation || fieldIsText || @@ -101,7 +107,8 @@ export const usePersistField = () => { fieldIsCurrency || fieldIsFullName || fieldIsSelect || - fieldIsAddress + fieldIsAddress || + fieldIsRawJson ) { const fieldName = fieldDefinition.metadata.fieldName; set( diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/JsonFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/JsonFieldDisplay.tsx new file mode 100644 index 000000000000..5a0f553cde44 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/JsonFieldDisplay.tsx @@ -0,0 +1,13 @@ +import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField'; +import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay'; + +export const JsonFieldDisplay = () => { + const { fieldValue, maxWidth } = useJsonField(); + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useJsonField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useJsonField.ts new file mode 100644 index 000000000000..0219eb17398b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useJsonField.ts @@ -0,0 +1,48 @@ +import { useContext } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; +import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldRawJson } from '../../types/guards/isFieldRawJson'; +import { isFieldTextValue } from '../../types/guards/isFieldTextValue'; + +export const useJsonField = () => { + const { entityId, fieldDefinition, hotkeyScope, maxWidth } = + useContext(FieldContext); + + assertFieldMetadata( + FieldMetadataType.RawJson, + isFieldRawJson, + fieldDefinition, + ); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + recordStoreFamilySelector({ + recordId: entityId, + fieldName: fieldName, + }), + ); + const fieldTextValue = isFieldTextValue(fieldValue) ? fieldValue : ''; + + const { setDraftValue, getDraftValueSelector } = + useRecordFieldInput(`${entityId}-${fieldName}`); + + const draftValue = useRecoilValue(getDraftValueSelector()); + + return { + draftValue, + setDraftValue, + maxWidth, + fieldDefinition, + fieldValue: fieldTextValue, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RawJsonFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RawJsonFieldInput.tsx new file mode 100644 index 000000000000..34a06e814144 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RawJsonFieldInput.tsx @@ -0,0 +1,83 @@ +import { isValidJSON } from '@/object-record/record-field/utils/isFieldValueJson'; +import { FieldTextAreaOverlay } from '@/ui/field/input/components/FieldTextAreaOverlay'; +import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useJsonField } from '../../hooks/useJsonField'; + +import { FieldInputEvent } from './DateFieldInput'; + +export type RawJsonFieldInputProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const RawJsonFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: RawJsonFieldInputProps) => { + const { fieldDefinition, draftValue, hotkeyScope, setDraftValue } = + useJsonField(); + + const persistField = usePersistField(); + + const handlePersistField = (newText: string) => { + if (!newText || isValidJSON(newText)) persistField(newText || null); + }; + + const handleEnter = (newText: string) => { + onEnter?.(() => handlePersistField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => handlePersistField(newText)); + }; + + const handleClickOutside = ( + _event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => handlePersistField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => handlePersistField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => handlePersistField(newText)); + }; + + const handleChange = (newText: string) => { + setDraftValue(newText); + }; + + const value = + draftValue && isValidJSON(draftValue) + ? JSON.stringify(JSON.parse(draftValue), null, 2) + : draftValue ?? ''; + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts index f682bd422944..d65d17bf6c7f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts @@ -78,6 +78,7 @@ export type FieldAddressMetadata = { export type FieldRawJsonMetadata = { objectMetadataNameSingular?: string; fieldName: string; + placeHolder: string; }; export type FieldDefinitionRelationType = @@ -146,3 +147,4 @@ export type FieldRatingValue = (typeof RATING_VALUES)[number]; export type FieldSelectValue = string | null; export type FieldRelationValue = EntityForSelect | null; +export type FieldJsonValue = string; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldRawJsonValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldRawJsonValue.ts new file mode 100644 index 000000000000..8c7657d8dc4b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldRawJsonValue.ts @@ -0,0 +1,8 @@ +import { isNull, isString } from '@sniptt/guards'; + +import { FieldJsonValue } from '../FieldMetadata'; + +// TODO: add zod +export const isFieldRawJsonValue = ( + fieldValue: unknown, +): fieldValue is FieldJsonValue => isString(fieldValue) || isNull(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts index 62ee380f7939..c20b06742d04 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts @@ -13,6 +13,7 @@ import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLi import { isFieldLinkValue } from '@/object-record/record-field/types/guards/isFieldLinkValue'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating'; +import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue'; @@ -39,7 +40,8 @@ export const isFieldValueEmpty = ({ isFieldRating(fieldDefinition) || isFieldEmail(fieldDefinition) || isFieldBoolean(fieldDefinition) || - isFieldRelation(fieldDefinition) + isFieldRelation(fieldDefinition) || + isFieldRawJson(fieldDefinition) //|| isFieldPhone(fieldDefinition) ) { return isValueEmpty(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueJson.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueJson.ts new file mode 100644 index 000000000000..6e577e7794d5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueJson.ts @@ -0,0 +1,12 @@ +import { isString } from '@sniptt/guards'; + +export const isValidJSON = (str: string) => { + try { + if (isString(JSON.parse(str))) { + throw new Error(`Strings are not supported as JSON: ${str}`); + } + return true; + } catch (error) { + return false; + } +}; diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index 2d8b314931f2..dd5d98f65255 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -75,6 +75,9 @@ export const generateEmptyFieldValue = ( case FieldMetadataType.MultiSelect: { throw new Error('Not implemented yet'); } + case FieldMetadataType.RawJson: { + return null; + } default: { throw new Error('Unhandled FieldMetadataType'); } diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts index d661a355c290..c4e2d798d418 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts @@ -2,6 +2,7 @@ import { IconCalendarEvent, IconCheck, IconCoins, + IconJson, IconKey, IconLink, IconMail, @@ -117,4 +118,9 @@ export const SETTINGS_FIELD_TYPE_CONFIGS: Record< addressLng: -118.2437, }, }, + [FieldMetadataType.RawJson]: { + label: 'JSON', + Icon: IconJson, + defaultValue: `{ "key": "value" }`, + }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 0cf85a8157fc..8a344827f409 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -69,6 +69,7 @@ const previewableTypes = [ FieldMetadataType.Relation, FieldMetadataType.Text, FieldMetadataType.Address, + FieldMetadataType.RawJson, ]; export const SettingsDataModelFieldSettingsFormCard = ({ diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts index 5a337be84666..0149601685e3 100644 --- a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts @@ -2,5 +2,5 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; export type SettingsSupportedFieldType = Exclude< FieldMetadataType, - FieldMetadataType.Position | FieldMetadataType.RawJson + FieldMetadataType.Position >; diff --git a/packages/twenty-front/src/modules/ui/display/tooltip/AppTooltip.tsx b/packages/twenty-front/src/modules/ui/display/tooltip/AppTooltip.tsx index 99fa35032fe4..ad8abe37afb8 100644 --- a/packages/twenty-front/src/modules/ui/display/tooltip/AppTooltip.tsx +++ b/packages/twenty-front/src/modules/ui/display/tooltip/AppTooltip.tsx @@ -35,6 +35,7 @@ export type AppTooltipProps = { className?: string; anchorSelect?: string; content?: string; + children?: React.ReactNode; delayHide?: number; offset?: number; noArrow?: boolean; @@ -53,6 +54,7 @@ export const AppTooltip = ({ offset, place, positionStrategy, + children, }: AppTooltipProps) => ( ); diff --git a/packages/twenty-front/src/modules/ui/display/tooltip/OverflowingTextWithTooltip.tsx b/packages/twenty-front/src/modules/ui/display/tooltip/OverflowingTextWithTooltip.tsx index 41b266efaf04..f8ee4ebcd484 100644 --- a/packages/twenty-front/src/modules/ui/display/tooltip/OverflowingTextWithTooltip.tsx +++ b/packages/twenty-front/src/modules/ui/display/tooltip/OverflowingTextWithTooltip.tsx @@ -22,9 +22,11 @@ const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>` export const OverflowingTextWithTooltip = ({ text, className, + mutliline, }: { text: string | null | undefined; className?: string; + mutliline?: boolean; }) => { const textElementId = `title-id-${uuidV4()}`; @@ -65,13 +67,15 @@ export const OverflowingTextWithTooltip = ({
+ > + {mutliline ?
{text}
: ''} +
, document.body, )} diff --git a/packages/twenty-front/src/modules/ui/field/display/components/JsonDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/JsonDisplay.tsx new file mode 100644 index 000000000000..63614d358c04 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/field/display/components/JsonDisplay.tsx @@ -0,0 +1,10 @@ +import { EllipsisDisplay } from './EllipsisDisplay'; + +type JsonDisplayProps = { + text: string; + maxWidth?: number; +}; + +export const JsonDisplay = ({ text, maxWidth }: JsonDisplayProps) => ( + {text} +); 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 43f9254bb6ef..29b754391362 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 @@ -19,6 +19,7 @@ export type TextAreaInputProps = { onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; hotkeyScope: string; onChange?: (newText: string) => void; + maxRows?: number; }; const StyledTextArea = styled(TextareaAutosize)` @@ -45,6 +46,7 @@ export const TextAreaInput = ({ onShiftTab, onClickOutside, onChange, + maxRows, }: TextAreaInputProps) => { const [internalText, setInternalText] = useState(value); @@ -84,6 +86,7 @@ export const TextAreaInput = ({ onChange={handleChange} autoFocus={autoFocus} value={internalText} + maxRows={maxRows} /> ); }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts index 32b0466f2060..b6c4f05a8474 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts @@ -1,3 +1,4 @@ +import { JsonScalarType } from './json.scalar'; import { PositionScalarType } from './position.scalar'; import { CursorScalarType } from './cursor.scalar'; import { BigFloatScalarType } from './big-float.scalar'; @@ -24,4 +25,5 @@ export const scalars = [ UUIDScalarType, CursorScalarType, PositionScalarType, + JsonScalarType, ]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar.ts new file mode 100644 index 000000000000..1f03304e715c --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar.ts @@ -0,0 +1,14 @@ +import { isString } from 'class-validator'; +import { GraphQLScalarType } from 'graphql'; +import GraphQLJSON from 'graphql-type-json'; + +export const JsonScalarType = new GraphQLScalarType({ + ...GraphQLJSON, + parseValue: (value) => { + if (isString(value) && isString(JSON.parse(value))) { + throw new Error(`Strings are not supported as JSON: ${value}`); + } + + return GraphQLJSON.parseValue(value); + }, +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index 2739f25949cf..3d5ca2f1b0d4 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -14,7 +14,6 @@ import { GraphQLString, GraphQLType, } from 'graphql'; -import GraphQLJSON from 'graphql-type-json'; import { DateScalarMode, @@ -39,6 +38,7 @@ import { UUIDScalarType, } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar'; +import { JsonScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar'; export interface TypeOptions { nullable?: boolean; @@ -71,7 +71,7 @@ export class TypeMapperService { [FieldMetadataType.PROBABILITY, GraphQLFloat], [FieldMetadataType.RELATION, UUIDScalarType], [FieldMetadataType.POSITION, PositionScalarType], - [FieldMetadataType.RAW_JSON, GraphQLJSON], + [FieldMetadataType.RAW_JSON, JsonScalarType], ]); return typeScalarMapping.get(fieldMetadataType); diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 75307797a23c..07401cb78491 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -72,7 +72,7 @@ const getSchemaComponentsProperties = ( }; break; case FieldMetadataType.RAW_JSON: - type: 'object'; + itemProperty.type = 'object'; break; default: itemProperty.type = 'string'; diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 25ec8383fa5d..f16f99dd205c 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -82,6 +82,7 @@ export { IconHierarchy2, IconInbox, IconInfoCircle, + IconJson, IconKey, IconLanguage, IconLayersLinked,