diff --git a/.github/workflows/docs-i18n-pull.yaml b/.github/workflows/docs-i18n-pull.yaml index 719cda6f65..a071290ddf 100644 --- a/.github/workflows/docs-i18n-pull.yaml +++ b/.github/workflows/docs-i18n-pull.yaml @@ -107,10 +107,13 @@ jobs: - name: Regenerate docs.json run: yarn docs:generate + - name: Regenerate documentation paths constants + run: yarn docs:generate-paths + - name: Commit artifacts to pull request branch if: github.event_name == 'pull_request' run: | - git add packages/twenty-docs/docs.json packages/twenty-docs/navigation/navigation.template.json + git add packages/twenty-docs/docs.json packages/twenty-docs/navigation/navigation.template.json packages/twenty-shared/src/constants/DocumentationPaths.ts if git diff --staged --quiet --exit-code; then echo "No navigation/doc changes to commit." exit 0 diff --git a/package.json b/package.json index adf5e2b661..1cca9f5d7a 100644 --- a/package.json +++ b/package.json @@ -210,6 +210,7 @@ "scripts": { "docs:generate": "tsx packages/twenty-docs/scripts/generate-docs-json.ts", "docs:generate-navigation-template": "tsx packages/twenty-docs/scripts/generate-navigation-template.ts", + "docs:generate-paths": "tsx packages/twenty-docs/scripts/generate-documentation-paths.ts", "start": "npx concurrently --kill-others 'npx nx run-many -t start -p twenty-server twenty-front' 'npx wait-on tcp:3000 && npx nx run twenty-server:worker'" }, "workspaces": { diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json index c5822db94d..c62c64c10c 100644 --- a/packages/twenty-docs/docs.json +++ b/packages/twenty-docs/docs.json @@ -80,6 +80,7 @@ "user-guide/data-model/how-tos/create-custom-objects", "user-guide/data-model/how-tos/create-custom-fields", "user-guide/data-model/how-tos/create-relation-fields", + "user-guide/data-model/how-tos/create-many-to-many-relations", "user-guide/data-model/how-tos/customize-your-data-model", "user-guide/data-model/how-tos/data-model-faq" ] @@ -524,6 +525,7 @@ "l/fr/user-guide/data-model/how-tos/create-custom-objects", "l/fr/user-guide/data-model/how-tos/create-custom-fields", "l/fr/user-guide/data-model/how-tos/create-relation-fields", + "l/fr/user-guide/data-model/how-tos/create-many-to-many-relations", "l/fr/user-guide/data-model/how-tos/customize-your-data-model", "l/fr/user-guide/data-model/how-tos/data-model-faq" ] @@ -968,6 +970,7 @@ "l/ar/user-guide/data-model/how-tos/create-custom-objects", "l/ar/user-guide/data-model/how-tos/create-custom-fields", "l/ar/user-guide/data-model/how-tos/create-relation-fields", + "l/ar/user-guide/data-model/how-tos/create-many-to-many-relations", "l/ar/user-guide/data-model/how-tos/customize-your-data-model", "l/ar/user-guide/data-model/how-tos/data-model-faq" ] @@ -1412,6 +1415,7 @@ "l/cs/user-guide/data-model/how-tos/create-custom-objects", "l/cs/user-guide/data-model/how-tos/create-custom-fields", "l/cs/user-guide/data-model/how-tos/create-relation-fields", + "l/cs/user-guide/data-model/how-tos/create-many-to-many-relations", "l/cs/user-guide/data-model/how-tos/customize-your-data-model", "l/cs/user-guide/data-model/how-tos/data-model-faq" ] @@ -1856,6 +1860,7 @@ "l/de/user-guide/data-model/how-tos/create-custom-objects", "l/de/user-guide/data-model/how-tos/create-custom-fields", "l/de/user-guide/data-model/how-tos/create-relation-fields", + "l/de/user-guide/data-model/how-tos/create-many-to-many-relations", "l/de/user-guide/data-model/how-tos/customize-your-data-model", "l/de/user-guide/data-model/how-tos/data-model-faq" ] @@ -2300,6 +2305,7 @@ "l/es/user-guide/data-model/how-tos/create-custom-objects", "l/es/user-guide/data-model/how-tos/create-custom-fields", "l/es/user-guide/data-model/how-tos/create-relation-fields", + "l/es/user-guide/data-model/how-tos/create-many-to-many-relations", "l/es/user-guide/data-model/how-tos/customize-your-data-model", "l/es/user-guide/data-model/how-tos/data-model-faq" ] @@ -2744,6 +2750,7 @@ "l/it/user-guide/data-model/how-tos/create-custom-objects", "l/it/user-guide/data-model/how-tos/create-custom-fields", "l/it/user-guide/data-model/how-tos/create-relation-fields", + "l/it/user-guide/data-model/how-tos/create-many-to-many-relations", "l/it/user-guide/data-model/how-tos/customize-your-data-model", "l/it/user-guide/data-model/how-tos/data-model-faq" ] @@ -3188,6 +3195,7 @@ "l/ja/user-guide/data-model/how-tos/create-custom-objects", "l/ja/user-guide/data-model/how-tos/create-custom-fields", "l/ja/user-guide/data-model/how-tos/create-relation-fields", + "l/ja/user-guide/data-model/how-tos/create-many-to-many-relations", "l/ja/user-guide/data-model/how-tos/customize-your-data-model", "l/ja/user-guide/data-model/how-tos/data-model-faq" ] @@ -3632,6 +3640,7 @@ "l/ko/user-guide/data-model/how-tos/create-custom-objects", "l/ko/user-guide/data-model/how-tos/create-custom-fields", "l/ko/user-guide/data-model/how-tos/create-relation-fields", + "l/ko/user-guide/data-model/how-tos/create-many-to-many-relations", "l/ko/user-guide/data-model/how-tos/customize-your-data-model", "l/ko/user-guide/data-model/how-tos/data-model-faq" ] @@ -4076,6 +4085,7 @@ "l/pt/user-guide/data-model/how-tos/create-custom-objects", "l/pt/user-guide/data-model/how-tos/create-custom-fields", "l/pt/user-guide/data-model/how-tos/create-relation-fields", + "l/pt/user-guide/data-model/how-tos/create-many-to-many-relations", "l/pt/user-guide/data-model/how-tos/customize-your-data-model", "l/pt/user-guide/data-model/how-tos/data-model-faq" ] @@ -4520,6 +4530,7 @@ "l/ro/user-guide/data-model/how-tos/create-custom-objects", "l/ro/user-guide/data-model/how-tos/create-custom-fields", "l/ro/user-guide/data-model/how-tos/create-relation-fields", + "l/ro/user-guide/data-model/how-tos/create-many-to-many-relations", "l/ro/user-guide/data-model/how-tos/customize-your-data-model", "l/ro/user-guide/data-model/how-tos/data-model-faq" ] @@ -4964,6 +4975,7 @@ "l/ru/user-guide/data-model/how-tos/create-custom-objects", "l/ru/user-guide/data-model/how-tos/create-custom-fields", "l/ru/user-guide/data-model/how-tos/create-relation-fields", + "l/ru/user-guide/data-model/how-tos/create-many-to-many-relations", "l/ru/user-guide/data-model/how-tos/customize-your-data-model", "l/ru/user-guide/data-model/how-tos/data-model-faq" ] @@ -5408,6 +5420,7 @@ "l/tr/user-guide/data-model/how-tos/create-custom-objects", "l/tr/user-guide/data-model/how-tos/create-custom-fields", "l/tr/user-guide/data-model/how-tos/create-relation-fields", + "l/tr/user-guide/data-model/how-tos/create-many-to-many-relations", "l/tr/user-guide/data-model/how-tos/customize-your-data-model", "l/tr/user-guide/data-model/how-tos/data-model-faq" ] @@ -5852,6 +5865,7 @@ "l/zh/user-guide/data-model/how-tos/create-custom-objects", "l/zh/user-guide/data-model/how-tos/create-custom-fields", "l/zh/user-guide/data-model/how-tos/create-relation-fields", + "l/zh/user-guide/data-model/how-tos/create-many-to-many-relations", "l/zh/user-guide/data-model/how-tos/customize-your-data-model", "l/zh/user-guide/data-model/how-tos/data-model-faq" ] diff --git a/packages/twenty-docs/navigation/base-structure.json b/packages/twenty-docs/navigation/base-structure.json index 5f6ff67299..8fe8d116e9 100644 --- a/packages/twenty-docs/navigation/base-structure.json +++ b/packages/twenty-docs/navigation/base-structure.json @@ -52,6 +52,7 @@ "user-guide/data-model/how-tos/create-custom-objects", "user-guide/data-model/how-tos/create-custom-fields", "user-guide/data-model/how-tos/create-relation-fields", + "user-guide/data-model/how-tos/create-many-to-many-relations", "user-guide/data-model/how-tos/customize-your-data-model", "user-guide/data-model/how-tos/data-model-faq" ] diff --git a/packages/twenty-docs/navigation/supported-languages.ts b/packages/twenty-docs/navigation/supported-languages.ts index c9680a1157..7ec016d6b1 100644 --- a/packages/twenty-docs/navigation/supported-languages.ts +++ b/packages/twenty-docs/navigation/supported-languages.ts @@ -1,21 +1,7 @@ -export const DEFAULT_LANGUAGE = 'en' as const; - -export const SUPPORTED_LANGUAGES = [ - DEFAULT_LANGUAGE, - 'fr', - 'ar', - 'cs', - 'de', - 'es', - 'it', - 'ja', - 'ko', - 'pt', - 'ro', - 'ru', - 'tr', - 'zh', -] as const; - -export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]; - +// Re-export from twenty-shared (source of truth) +// Using relative path because these scripts run via tsx from workspace root +export { DOCUMENTATION_DEFAULT_LANGUAGE as DEFAULT_LANGUAGE } from '../../twenty-shared/src/constants/DocumentationDefaultLanguage'; +export { + DOCUMENTATION_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, + type DocumentationSupportedLanguage as SupportedLanguage, +} from '../../twenty-shared/src/constants/DocumentationSupportedLanguages'; diff --git a/packages/twenty-docs/package.json b/packages/twenty-docs/package.json index 9753a1a065..03cc8ba7ae 100644 --- a/packages/twenty-docs/package.json +++ b/packages/twenty-docs/package.json @@ -13,6 +13,9 @@ "dependencies": { "mintlify": "latest" }, + "devDependencies": { + "twenty-shared": "workspace:*" + }, "engines": { "node": "^24.5.0", "npm": "please-use-yarn", diff --git a/packages/twenty-docs/scripts/generate-documentation-paths.ts b/packages/twenty-docs/scripts/generate-documentation-paths.ts new file mode 100644 index 0000000000..81fe99ffbb --- /dev/null +++ b/packages/twenty-docs/scripts/generate-documentation-paths.ts @@ -0,0 +1,101 @@ +import fs from 'fs'; +import path from 'path'; + +type BasePage = string | BaseGroup; + +type BaseGroup = { + key: string; + label: string; + icon?: string; + pages: BasePage[]; +}; + +type BaseTab = { + key: string; + label: string; + groups: BaseGroup[]; +}; + +type BaseStructure = { + tabs: BaseTab[]; +}; + +const baseStructurePath = path.resolve( + __dirname, + '../navigation/base-structure.json', +); + +const outputDir = path.resolve(__dirname, '../../twenty-shared/src/constants'); + +const baseStructure: BaseStructure = JSON.parse( + fs.readFileSync(baseStructurePath, 'utf8'), +); + +const extractPaths = (pages: BasePage[]): string[] => { + const paths: string[] = []; + + for (const page of pages) { + if (typeof page === 'string') { + paths.push(page); + } else { + paths.push(...extractPaths(page.pages)); + } + } + + return paths; +}; + +const pathToConstantName = (docPath: string): string => { + return docPath.replace(/^\//, '').replace(/[/-]/g, '_').toUpperCase(); +}; + +const allPaths: string[] = []; + +for (const tab of baseStructure.tabs) { + for (const group of tab.groups) { + allPaths.push(...extractPaths(group.pages)); + } +} + +const sortedPaths = [...allPaths].sort(); + +const AUTO_GENERATED_HEADER = `/* + * _____ _ + *|_ _|_ _____ _ __ | |_ _ _ + * | | \\ \\ /\\ / / _ \\ '_ \\| __| | | | Auto-generated file + * | | \\ V V / __/ | | | |_| |_| | DO NOT EDIT - changes will be overwritten + * |_| \\_/\\_/ \\___|_| |_|\\__|\\__, | Generated by: yarn docs:generate-paths + * |___/ + * + * This file is generated from packages/twenty-docs/navigation/base-structure.json + * To add new documentation pages, add them to base-structure.json and run: + * yarn docs:generate-paths + */ + +`; + +// Generate DocumentationPaths.ts +const pathEntries = sortedPaths + .map((docPath) => { + const constName = pathToConstantName(docPath); + const value = `'/${docPath}'`; + // Check if line would be too long (80 char limit) + if (` ${constName}: ${value},`.length > 80) { + return ` ${constName}:\n ${value},`; + } + return ` ${constName}: ${value},`; + }) + .join('\n'); + +const pathsContent = `${AUTO_GENERATED_HEADER}export const DOCUMENTATION_PATHS = { +${pathEntries} +} as const; + +export type DocumentationPath = + (typeof DOCUMENTATION_PATHS)[keyof typeof DOCUMENTATION_PATHS]; +`; + +fs.writeFileSync(path.join(outputDir, 'DocumentationPaths.ts'), pathsContent); + +console.log(`Generated documentation constants to ${outputDir}`); +console.log(`Total paths: ${sortedPaths.length}`); diff --git a/packages/twenty-docs/user-guide/data-model/capabilities/relation-fields.mdx b/packages/twenty-docs/user-guide/data-model/capabilities/relation-fields.mdx index accecc4658..d26b21ce9f 100644 --- a/packages/twenty-docs/user-guide/data-model/capabilities/relation-fields.mdx +++ b/packages/twenty-docs/user-guide/data-model/capabilities/relation-fields.mdx @@ -37,12 +37,16 @@ Many records in Object A can be linked to many records in Object B. **Example:** Many People can be linked to many Projects, and vice versa. - -**Many-to-Many is not yet supported.** +Many-to-many relations use a **junction object** pattern: an intermediate object that connects both sides. With the junction relation feature, Twenty displays the final linked records directly, hiding the intermediate object from the UI. -This relation type is planned for H1 2026. As a workaround, create an intermediate "junction" object (e.g., "Project Assignments") that has Many-to-One relations to both objects. + + + +**Lab Feature**: Junction relations must be enabled at **Settings → Updates → Lab** before use. +See [How to Create Many-to-Many Relations](/user-guide/data-model/how-tos/create-many-to-many-relations) for a complete step-by-step guide. + ## Creating a Relation Field 1. Go to **Settings → Data Model** diff --git a/packages/twenty-docs/user-guide/data-model/how-tos/create-many-to-many-relations.mdx b/packages/twenty-docs/user-guide/data-model/how-tos/create-many-to-many-relations.mdx new file mode 100644 index 0000000000..17e6a7d362 --- /dev/null +++ b/packages/twenty-docs/user-guide/data-model/how-tos/create-many-to-many-relations.mdx @@ -0,0 +1,175 @@ +--- +title: Create Many-to-Many Relations +description: Connect records where many items on both sides can be linked together using junction objects. +--- + +Many-to-many relations let you connect multiple records on both sides. For example: many People can work on many Projects, and each Project can have many People. + + +**Lab Feature**: Junction relations are currently in the Lab. Enable them at **Settings → Updates → Lab** before following this guide. + + + +This feature also requires **Advanced mode** to be enabled (toggle at the bottom right of Settings). + + +## When to Use Many-to-Many + +Use many-to-many when both sides of a relationship can have multiple connections: + +| Relationship | Example | +|-------------|---------| +| People ↔ Projects | A person works on multiple projects; a project has multiple team members | +| Companies ↔ Tags | A company can have multiple tags; a tag can apply to multiple companies | +| Products ↔ Orders | A product can be in multiple orders; an order contains multiple products | + +## How It Works + +Twenty uses a **junction object** pattern for many-to-many relations. A junction object sits between two objects and holds the connections: + +``` +People ←→ Project Assignments ←→ Projects +``` + +The **Project Assignments** object (junction) has: +- A relation to People (many-to-one) +- A relation to Projects (many-to-one) + +When you enable the junction relation toggle, Twenty displays linked records directly instead of showing the intermediate junction records. + +## Prerequisites + +1. **Enable Junction Relations in Lab**: Go to **Settings → Updates → Lab** and enable **Junction Relations** +2. **Enable Advanced mode**: Toggle on **Advanced mode** at the bottom right of the Settings sidebar +3. Plan your data model: + - Which two objects are you connecting? + - What should the junction object be called? + +## Step 1: Create the Junction Object + +First, create the intermediate object that will hold the connections. + +1. Go to **Settings → Data Model** +2. Click **+ New object** +3. Name it descriptively (e.g., "Project Assignment", "Team Member", "Product Order") +4. Click **Save** + + +**Naming convention**: Use a name that describes the relationship, like "Project Assignment" or "Team Membership". This makes the data model easier to understand. + + +## Step 2: Create Relations from the Junction Object + +Add relation fields from the junction object to both objects you want to connect. + +### First Relation (Junction → Object A) + +1. Select your junction object in **Settings → Data Model** +2. Click **+ Add Field** +3. Choose **Relation** as the field type +4. Select the first object (e.g., "People") +5. Set the relation type to **Many-to-One** (many assignments can link to one person) +6. Name the fields: + - Field on junction: e.g., "Person" + - Field on People: e.g., "Project Assignments" +7. Click **Save** + +### Second Relation (Junction → Object B) + +1. Still on the junction object, click **+ Add Field** +2. Choose **Relation** as the field type +3. Select the second object (e.g., "Projects") +4. Set the relation type to **Many-to-One** +5. Name the fields: + - Field on junction: e.g., "Project" + - Field on Projects: e.g., "Team Members" +6. Click **Save** + +## Step 3: Configure the Junction Relation Display + +Now configure the source objects to display linked records directly, skipping the intermediate junction object. + +1. Go to **Settings → Data Model** +2. Select the first object (e.g., "People") +3. Find the relation field pointing to the junction object (e.g., "Project Assignments") +4. Click to edit the field +5. Enable **"This is a relation to a Junction Object"** +6. Select the **Target relation** (e.g., "Project" — the field on the junction that points to the other side) +7. Click **Save** + + +{/* TODO: Add image + +*/} + +Repeat for the other object: +1. Select "Projects" in Data Model +2. Edit the "Team Members" relation field +3. Enable the junction toggle +4. Select "Person" as the target relation +5. Save + +## Result + +After configuration: + +- On a **Person** record, the "Project Assignments" field displays **Projects** directly (not assignment records) +- On a **Project** record, the "Team Members" field displays **People** directly + +The junction object still exists and stores the connections, but the UI presents a cleaner many-to-many view. + +## Example: People ↔ Projects + +Here's a complete walkthrough: + +### Create the Junction Object +- Name: **Project Assignment** +- Description: "Links people to projects they work on" + +### Add Relations +1. **Project Assignment → People** + - Type: Many-to-One + - Field on Assignment: "Person" + - Field on People: "Project Assignments" + +2. **Project Assignment → Projects** + - Type: Many-to-One + - Field on Assignment: "Project" + - Field on Projects: "Team Members" + +### Configure Junction Display +1. On **People** object: + - Edit "Project Assignments" field + - Enable junction toggle + - Target: "Project" + +2. On **Projects** object: + - Edit "Team Members" field + - Enable junction toggle + - Target: "Person" + +### Use It +- Open a Person record → See their Projects directly +- Open a Project record → See team members directly +- Create new connections from either side + +## Adding Extra Data to Connections + +Since the junction object is a real object, you can add custom fields to store information about the relationship: + +- **Role**: "Developer", "Designer", "Manager" +- **Start Date**: When they joined the project +- **Hours Allocated**: Weekly hours on this project + +To access this data, navigate to the junction object directly or query it via the API. + +## Limitations + +- **CSV Import/Export**: Importing many-to-many relations directly is not supported. Import records to the junction object instead. +- **Filters**: Filtering by many-to-many relations may have limited options. + +## Related + +- [Relation Fields](/user-guide/data-model/capabilities/relation-fields) — relation types explained +- [Create Custom Objects](/user-guide/data-model/how-tos/create-custom-objects) — how to create objects +- [Create Relation Fields](/user-guide/data-model/how-tos/create-relation-fields) — basic relation setup diff --git a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentBase.tsx b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentBase.tsx index 429ce45676..4ebe90e967 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentBase.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentBase.tsx @@ -32,4 +32,10 @@ export const StyledSettingsOptionCardTitle = styled.div` export const StyledSettingsOptionCardDescription = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; font-size: ${({ theme }) => theme.font.size.sm}; + + a { + position: relative; + z-index: 1; + pointer-events: auto; + } `; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentToggle.tsx b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentToggle.tsx index d2fca73fc7..31350c4946 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentToggle.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentToggle.tsx @@ -41,7 +41,7 @@ const StyledSettingsOptionCardToggleCover = styled.span` type SettingsOptionCardContentToggleProps = { Icon?: IconComponent; title: React.ReactNode; - description?: string; + description?: React.ReactNode; divider?: boolean; disabled?: boolean; advancedMode?: boolean; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx index cfffc5bd14..2637b36181 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationForm.tsx @@ -55,6 +55,12 @@ export const settingsDataModelFieldMorphRelationFormSchema = z.object({ ), targetFieldLabel: z.string().min(1), iconOnDestination: z.string().min(1), + settings: z + .object({ + junctionTargetFieldId: z.string().optional(), + }) + .catchall(z.unknown()) + .optional(), }); export type SettingsDataModelFieldMorphRelationFormValues = z.infer< diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationJunctionForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationJunctionForm.tsx index 3c8b796afb..b06e1ccbf0 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationJunctionForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/morph-relation/components/SettingsDataModelFieldRelationJunctionForm.tsx @@ -1,16 +1,19 @@ +import { Trans, useLingui } from '@lingui/react/macro'; import { useFormContext } from 'react-hook-form'; +import { useRecoilValue } from 'recoil'; +import { DOCUMENTATION_PATHS } from 'twenty-shared/constants'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { IconLink } from 'twenty-ui/display'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; +import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; import { Select } from '@/ui/input/components/Select'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; -import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; -import { FieldMetadataType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { IconLink } from 'twenty-ui/display'; import { RelationType } from '~/generated-metadata/graphql'; import { type SettingsDataModelFieldEditFormValues } from '~/pages/settings/data-model/SettingsObjectFieldEdit'; @@ -26,6 +29,12 @@ export const SettingsDataModelFieldRelationJunctionForm = ({ useFormContext(); const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + + const documentationUrl = getDocumentationUrl({ + locale: currentWorkspaceMember?.locale, + path: DOCUMENTATION_PATHS.USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_MANY_TO_MANY_RELATIONS, + }); const { objectMetadataItem: sourceObjectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); @@ -34,7 +43,8 @@ export const SettingsDataModelFieldRelationJunctionForm = ({ const relationType = watch('relationType') ?? RelationType.ONE_TO_MANY; const targetObjectIds = watch('morphRelationObjectMetadataIds') ?? []; - const junctionTargetFieldId = watch('settings.junctionTargetFieldId'); + const currentSettings = watch('settings'); + const junctionTargetFieldId = currentSettings?.junctionTargetFieldId; // Only applies to ONE_TO_MANY with single target if ( @@ -112,23 +122,40 @@ export const SettingsDataModelFieldRelationJunctionForm = ({ const handleJunctionToggle = (checked: boolean) => { if (checked && junctionFieldOptions.length > 0) { setValue( - 'settings.junctionTargetFieldId', - junctionFieldOptions[0].value, + 'settings', + { + ...currentSettings, + junctionTargetFieldId: junctionFieldOptions[0].value, + }, { shouldDirty: true, }, ); } else { - setValue('settings.junctionTargetFieldId', undefined, { - shouldDirty: true, - }); + setValue( + 'settings', + { + ...currentSettings, + junctionTargetFieldId: undefined, + }, + { + shouldDirty: true, + }, + ); } }; const handleSelectionChange = (selectedValue: string) => { - setValue('settings.junctionTargetFieldId', selectedValue, { - shouldDirty: true, - }); + setValue( + 'settings', + { + ...currentSettings, + junctionTargetFieldId: selectedValue, + }, + { + shouldDirty: true, + }, + ); }; return ( @@ -136,7 +163,19 @@ export const SettingsDataModelFieldRelationJunctionForm = ({ + Build many-to-many relations.{' '} + + Learn more + + + } checked={isJunctionConfigEnabled} onChange={handleJunctionToggle} divider={isJunctionConfigEnabled} diff --git a/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts b/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts index c7ac1d8b79..f1b25214cd 100644 --- a/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts +++ b/packages/twenty-front/src/modules/support/utils/getDocumentationUrl.ts @@ -1,15 +1,17 @@ -const DOCUMENTATION_BASE_URL = 'https://docs.twenty.com'; -const DOCUMENTATION_PATH = '/user-guide/introduction'; - -// Locales that have documentation translations available -const SUPPORTED_DOC_LOCALES = ['fr', 'pt', 'de', 'es', 'it', 'ja', 'ko', 'zh']; +import { + DOCUMENTATION_BASE_URL, + DOCUMENTATION_DEFAULT_LANGUAGE, + DOCUMENTATION_DEFAULT_PATH, + DOCUMENTATION_SUPPORTED_LANGUAGES, + type DocumentationPath, +} from 'twenty-shared/constants'; export const getDocumentationUrl = ({ locale, - path = DOCUMENTATION_PATH, + path = DOCUMENTATION_DEFAULT_PATH, }: { locale?: string | null; - path?: string; + path?: DocumentationPath | string; }): string => { if (!locale) { return `${DOCUMENTATION_BASE_URL}${path}`; @@ -18,7 +20,16 @@ export const getDocumentationUrl = ({ // Extract language code from locale (e.g., 'fr' from 'fr-FR') const langCode = locale.split('-')[0].toLowerCase(); - if (SUPPORTED_DOC_LOCALES.includes(langCode)) { + // English content is served at root path (no /l/en/ prefix) + if (langCode === DOCUMENTATION_DEFAULT_LANGUAGE) { + return `${DOCUMENTATION_BASE_URL}${path}`; + } + + const isSupported = DOCUMENTATION_SUPPORTED_LANGUAGES.includes( + langCode as (typeof DOCUMENTATION_SUPPORTED_LANGUAGES)[number], + ); + + if (isSupported) { return `${DOCUMENTATION_BASE_URL}/l/${langCode}${path}`; } diff --git a/packages/twenty-shared/src/constants/DocumentationBaseUrl.ts b/packages/twenty-shared/src/constants/DocumentationBaseUrl.ts new file mode 100644 index 0000000000..d772b12d27 --- /dev/null +++ b/packages/twenty-shared/src/constants/DocumentationBaseUrl.ts @@ -0,0 +1 @@ +export const DOCUMENTATION_BASE_URL = 'https://docs.twenty.com'; diff --git a/packages/twenty-shared/src/constants/DocumentationDefaultLanguage.ts b/packages/twenty-shared/src/constants/DocumentationDefaultLanguage.ts new file mode 100644 index 0000000000..6f65449041 --- /dev/null +++ b/packages/twenty-shared/src/constants/DocumentationDefaultLanguage.ts @@ -0,0 +1 @@ +export const DOCUMENTATION_DEFAULT_LANGUAGE = 'en' as const; diff --git a/packages/twenty-shared/src/constants/DocumentationDefaultPath.ts b/packages/twenty-shared/src/constants/DocumentationDefaultPath.ts new file mode 100644 index 0000000000..35e467b5f7 --- /dev/null +++ b/packages/twenty-shared/src/constants/DocumentationDefaultPath.ts @@ -0,0 +1,4 @@ +import { DOCUMENTATION_PATHS } from './DocumentationPaths'; + +export const DOCUMENTATION_DEFAULT_PATH = + DOCUMENTATION_PATHS.USER_GUIDE_INTRODUCTION; diff --git a/packages/twenty-shared/src/constants/DocumentationPaths.ts b/packages/twenty-shared/src/constants/DocumentationPaths.ts new file mode 100644 index 0000000000..40433e23a2 --- /dev/null +++ b/packages/twenty-shared/src/constants/DocumentationPaths.ts @@ -0,0 +1,298 @@ +/* + * _____ _ + *|_ _|_ _____ _ __ | |_ _ _ + * | | \ \ /\ / / _ \ '_ \| __| | | | Auto-generated file + * | | \ V V / __/ | | | |_| |_| | DO NOT EDIT - changes will be overwritten + * |_| \_/\_/ \___|_| |_|\__|\__, | Generated by: yarn docs:generate-paths + * |___/ + * + * This file is generated from packages/twenty-docs/navigation/base-structure.json + * To add new documentation pages, add them to base-structure.json and run: + * yarn docs:generate-paths + */ + +export const DOCUMENTATION_PATHS = { + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_BEST_PRACTICES_SERVER: + '/developers/contribute/capabilities/backend-development/best-practices-server', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_CUSTOM_OBJECTS: + '/developers/contribute/capabilities/backend-development/custom-objects', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_FEATURE_FLAGS: + '/developers/contribute/capabilities/backend-development/feature-flags', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_FOLDER_ARCHITECTURE_SERVER: + '/developers/contribute/capabilities/backend-development/folder-architecture-server', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_QUEUE: + '/developers/contribute/capabilities/backend-development/queue', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_SERVER_COMMANDS: + '/developers/contribute/capabilities/backend-development/server-commands', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BACKEND_DEVELOPMENT_ZAPIER: + '/developers/contribute/capabilities/backend-development/zapier', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_BUG_AND_REQUESTS: + '/developers/contribute/capabilities/bug-and-requests', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_BEST_PRACTICES_FRONT: + '/developers/contribute/capabilities/frontend-development/best-practices-front', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_FOLDER_ARCHITECTURE_FRONT: + '/developers/contribute/capabilities/frontend-development/folder-architecture-front', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_FRONTEND_COMMANDS: + '/developers/contribute/capabilities/frontend-development/frontend-commands', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_HOTKEYS: + '/developers/contribute/capabilities/frontend-development/hotkeys', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_STORYBOOK: + '/developers/contribute/capabilities/frontend-development/storybook', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_STYLE_GUIDE: + '/developers/contribute/capabilities/frontend-development/style-guide', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_FRONTEND_DEVELOPMENT_WORK_WITH_FIGMA: + '/developers/contribute/capabilities/frontend-development/work-with-figma', + DEVELOPERS_CONTRIBUTE_CAPABILITIES_LOCAL_SETUP: + '/developers/contribute/capabilities/local-setup', + DEVELOPERS_CONTRIBUTE_CONTRIBUTE: '/developers/contribute/contribute', + DEVELOPERS_EXTEND_CAPABILITIES_APIS: '/developers/extend/capabilities/apis', + DEVELOPERS_EXTEND_CAPABILITIES_APPS: '/developers/extend/capabilities/apps', + DEVELOPERS_EXTEND_CAPABILITIES_WEBHOOKS: + '/developers/extend/capabilities/webhooks', + DEVELOPERS_EXTEND_EXTEND: '/developers/extend/extend', + DEVELOPERS_INTRODUCTION: '/developers/introduction', + DEVELOPERS_SELF_HOST_CAPABILITIES_CLOUD_PROVIDERS: + '/developers/self-host/capabilities/cloud-providers', + DEVELOPERS_SELF_HOST_CAPABILITIES_DOCKER_COMPOSE: + '/developers/self-host/capabilities/docker-compose', + DEVELOPERS_SELF_HOST_CAPABILITIES_SETUP: + '/developers/self-host/capabilities/setup', + DEVELOPERS_SELF_HOST_CAPABILITIES_TROUBLESHOOTING: + '/developers/self-host/capabilities/troubleshooting', + DEVELOPERS_SELF_HOST_CAPABILITIES_UPGRADE_GUIDE: + '/developers/self-host/capabilities/upgrade-guide', + DEVELOPERS_SELF_HOST_SELF_HOST: '/developers/self-host/self-host', + TWENTY_UI_DISPLAY_APP_TOOLTIP: '/twenty-ui/display/app-tooltip', + TWENTY_UI_DISPLAY_CHECKMARK: '/twenty-ui/display/checkmark', + TWENTY_UI_DISPLAY_CHIP: '/twenty-ui/display/chip', + TWENTY_UI_DISPLAY_ICONS: '/twenty-ui/display/icons', + TWENTY_UI_DISPLAY_SOON_PILL: '/twenty-ui/display/soon-pill', + TWENTY_UI_DISPLAY_TAG: '/twenty-ui/display/tag', + TWENTY_UI_INPUT_BLOCK_EDITOR: '/twenty-ui/input/block-editor', + TWENTY_UI_INPUT_BUTTONS: '/twenty-ui/input/buttons', + TWENTY_UI_INPUT_CHECKBOX: '/twenty-ui/input/checkbox', + TWENTY_UI_INPUT_COLOR_SCHEME: '/twenty-ui/input/color-scheme', + TWENTY_UI_INPUT_ICON_PICKER: '/twenty-ui/input/icon-picker', + TWENTY_UI_INPUT_IMAGE_INPUT: '/twenty-ui/input/image-input', + TWENTY_UI_INPUT_RADIO: '/twenty-ui/input/radio', + TWENTY_UI_INPUT_SELECT: '/twenty-ui/input/select', + TWENTY_UI_INPUT_TEXT: '/twenty-ui/input/text', + TWENTY_UI_INPUT_TOGGLE: '/twenty-ui/input/toggle', + TWENTY_UI_INTRODUCTION: '/twenty-ui/introduction', + TWENTY_UI_NAVIGATION: '/twenty-ui/navigation', + TWENTY_UI_NAVIGATION_BREADCRUMB: '/twenty-ui/navigation/breadcrumb', + TWENTY_UI_NAVIGATION_LINKS: '/twenty-ui/navigation/links', + TWENTY_UI_NAVIGATION_MENU_ITEM: '/twenty-ui/navigation/menu-item', + TWENTY_UI_NAVIGATION_NAVIGATION_BAR: '/twenty-ui/navigation/navigation-bar', + TWENTY_UI_NAVIGATION_STEP_BAR: '/twenty-ui/navigation/step-bar', + TWENTY_UI_PROGRESS_BAR: '/twenty-ui/progress-bar', + USER_GUIDE_AI_CAPABILITIES_AI_AGENTS: '/user-guide/ai/capabilities/ai-agents', + USER_GUIDE_AI_CAPABILITIES_AI_CHATBOT: + '/user-guide/ai/capabilities/ai-chatbot', + USER_GUIDE_AI_CAPABILITIES_PERMISSIONS_ACCESS_CONTROL: + '/user-guide/ai/capabilities/permissions-access-control', + USER_GUIDE_AI_HOW_TOS_AI_FAQ: '/user-guide/ai/how-tos/ai-faq', + USER_GUIDE_AI_OVERVIEW: '/user-guide/ai/overview', + USER_GUIDE_BILLING_CAPABILITIES_PRICING_PLANS: + '/user-guide/billing/capabilities/pricing-plans', + USER_GUIDE_BILLING_CAPABILITIES_WORKFLOW_CREDITS: + '/user-guide/billing/capabilities/workflow-credits', + USER_GUIDE_BILLING_HOW_TOS_BILLING_FAQ: + '/user-guide/billing/how-tos/billing-faq', + USER_GUIDE_BILLING_OVERVIEW: '/user-guide/billing/overview', + USER_GUIDE_CALENDAR_EMAILS_CAPABILITIES_CALENDAR: + '/user-guide/calendar-emails/capabilities/calendar', + USER_GUIDE_CALENDAR_EMAILS_CAPABILITIES_MAILBOX: + '/user-guide/calendar-emails/capabilities/mailbox', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_CAN_I_BOOK_MEETINGS_FROM_TWENTY: + '/user-guide/calendar-emails/how-tos/can-i-book-meetings-from-twenty', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_CAN_I_SEND_EMAILS_FROM_TWENTY: + '/user-guide/calendar-emails/how-tos/can-i-send-emails-from-twenty', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_CAN_I_TRACK_EMAIL_ACTIVITY_ON_ALL_OBJECTS: + '/user-guide/calendar-emails/how-tos/can-i-track-email-activity-on-all-objects', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_CONNECT_SEVERAL_MAILBOXES_PER_USER: + '/user-guide/calendar-emails/how-tos/connect-several-mailboxes-per-user', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_I_DONT_SEE_EMAILS_ON_RECORDS: + '/user-guide/calendar-emails/how-tos/i-dont-see-emails-on-records', + USER_GUIDE_CALENDAR_EMAILS_HOW_TOS_LIMIT_EMAILS_IMPORTED: + '/user-guide/calendar-emails/how-tos/limit-emails-imported', + USER_GUIDE_CALENDAR_EMAILS_OVERVIEW: '/user-guide/calendar-emails/overview', + USER_GUIDE_DASHBOARDS_CAPABILITIES_CHART_SETTINGS: + '/user-guide/dashboards/capabilities/chart-settings', + USER_GUIDE_DASHBOARDS_CAPABILITIES_DASHBOARDS: + '/user-guide/dashboards/capabilities/dashboards', + USER_GUIDE_DASHBOARDS_CAPABILITIES_WIDGETS: + '/user-guide/dashboards/capabilities/widgets', + USER_GUIDE_DASHBOARDS_HOW_TOS_DASHBOARDS_FAQ: + '/user-guide/dashboards/how-tos/dashboards-faq', + USER_GUIDE_DASHBOARDS_HOW_TOS_WIDGET_FAQ: + '/user-guide/dashboards/how-tos/widget-faq', + USER_GUIDE_DASHBOARDS_OVERVIEW: '/user-guide/dashboards/overview', + USER_GUIDE_DATA_MIGRATION_CAPABILITIES_ERROR_HANDLING: + '/user-guide/data-migration/capabilities/error-handling', + USER_GUIDE_DATA_MIGRATION_CAPABILITIES_FIELD_MAPPING: + '/user-guide/data-migration/capabilities/field-mapping', + USER_GUIDE_DATA_MIGRATION_CAPABILITIES_FILE_FORMATS: + '/user-guide/data-migration/capabilities/file-formats', + USER_GUIDE_DATA_MIGRATION_CAPABILITIES_IMPORT_RELATIONS: + '/user-guide/data-migration/capabilities/import-relations', + USER_GUIDE_DATA_MIGRATION_CAPABILITIES_UNIQUENESS_CONSTRAINTS: + '/user-guide/data-migration/capabilities/uniqueness-constraints', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_EXPORT_YOUR_DATA: + '/user-guide/data-migration/how-tos/export-your-data', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_FIX_IMPORT_ERRORS: + '/user-guide/data-migration/how-tos/fix-import-errors', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_IMPORT_COMPANIES_VIA_CSV: + '/user-guide/data-migration/how-tos/import-companies-via-csv', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_IMPORT_CONTACTS_VIA_CSV: + '/user-guide/data-migration/how-tos/import-contacts-via-csv', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_IMPORT_DATA_VIA_API: + '/user-guide/data-migration/how-tos/import-data-via-api', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_IMPORT_RELATIONS_BETWEEN_OBJECTS_VIA_CSV: + '/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_MIGRATING_FROM_OTHER_CRMS: + '/user-guide/data-migration/how-tos/migrating-from-other-crms', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_MIGRATING_FROM_SELF_HOSTED_TO_CLOUD: + '/user-guide/data-migration/how-tos/migrating-from-self-hosted-to-cloud', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_PREPARE_YOUR_CSV_FILES: + '/user-guide/data-migration/how-tos/prepare-your-csv-files', + USER_GUIDE_DATA_MIGRATION_HOW_TOS_UPDATE_EXISTING_RECORDS_VIA_IMPORT: + '/user-guide/data-migration/how-tos/update-existing-records-via-import', + USER_GUIDE_DATA_MIGRATION_OVERVIEW: '/user-guide/data-migration/overview', + USER_GUIDE_DATA_MODEL_CAPABILITIES_FIELDS: + '/user-guide/data-model/capabilities/fields', + USER_GUIDE_DATA_MODEL_CAPABILITIES_OBJECTS: + '/user-guide/data-model/capabilities/objects', + USER_GUIDE_DATA_MODEL_CAPABILITIES_RELATION_FIELDS: + '/user-guide/data-model/capabilities/relation-fields', + USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_CUSTOM_FIELDS: + '/user-guide/data-model/how-tos/create-custom-fields', + USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_CUSTOM_OBJECTS: + '/user-guide/data-model/how-tos/create-custom-objects', + USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_MANY_TO_MANY_RELATIONS: + '/user-guide/data-model/how-tos/create-many-to-many-relations', + USER_GUIDE_DATA_MODEL_HOW_TOS_CREATE_RELATION_FIELDS: + '/user-guide/data-model/how-tos/create-relation-fields', + USER_GUIDE_DATA_MODEL_HOW_TOS_CUSTOMIZE_YOUR_DATA_MODEL: + '/user-guide/data-model/how-tos/customize-your-data-model', + USER_GUIDE_DATA_MODEL_HOW_TOS_DATA_MODEL_FAQ: + '/user-guide/data-model/how-tos/data-model-faq', + USER_GUIDE_DATA_MODEL_OVERVIEW: '/user-guide/data-model/overview', + USER_GUIDE_GETTING_STARTED_CAPABILITIES_GLOSSARY: + '/user-guide/getting-started/capabilities/glossary', + USER_GUIDE_GETTING_STARTED_CAPABILITIES_IMPLEMENTATION_SERVICES: + '/user-guide/getting-started/capabilities/implementation-services', + USER_GUIDE_GETTING_STARTED_CAPABILITIES_WHAT_IS_TWENTY: + '/user-guide/getting-started/capabilities/what-is-twenty', + USER_GUIDE_GETTING_STARTED_HOW_TOS_CONFIGURE_YOUR_WORKSPACE: + '/user-guide/getting-started/how-tos/configure-your-workspace', + USER_GUIDE_GETTING_STARTED_HOW_TOS_CREATE_WORKSPACE: + '/user-guide/getting-started/how-tos/create-workspace', + USER_GUIDE_GETTING_STARTED_HOW_TOS_NAVIGATE_AROUND_TWENTY: + '/user-guide/getting-started/how-tos/navigate-around-twenty', + USER_GUIDE_INTRODUCTION: '/user-guide/introduction', + USER_GUIDE_PERMISSIONS_ACCESS_CAPABILITIES_PERMISSIONS: + '/user-guide/permissions-access/capabilities/permissions', + USER_GUIDE_PERMISSIONS_ACCESS_CAPABILITIES_SSO_CONFIGURATION: + '/user-guide/permissions-access/capabilities/sso-configuration', + USER_GUIDE_PERMISSIONS_ACCESS_HOW_TOS_PERMISSIONS_FAQ: + '/user-guide/permissions-access/how-tos/permissions-faq', + USER_GUIDE_PERMISSIONS_ACCESS_OVERVIEW: + '/user-guide/permissions-access/overview', + USER_GUIDE_SETTINGS_CAPABILITIES_DOMAINS_SETTINGS: + '/user-guide/settings/capabilities/domains-settings', + USER_GUIDE_SETTINGS_CAPABILITIES_EXPERIENCE_SETTINGS: + '/user-guide/settings/capabilities/experience-settings', + USER_GUIDE_SETTINGS_CAPABILITIES_MEMBER_MANAGEMENT: + '/user-guide/settings/capabilities/member-management', + USER_GUIDE_SETTINGS_CAPABILITIES_PROFILE_SETTINGS: + '/user-guide/settings/capabilities/profile-settings', + USER_GUIDE_SETTINGS_CAPABILITIES_UPDATES_SETTINGS: + '/user-guide/settings/capabilities/updates-settings', + USER_GUIDE_SETTINGS_CAPABILITIES_WORKSPACE_SETTINGS: + '/user-guide/settings/capabilities/workspace-settings', + USER_GUIDE_SETTINGS_HOW_TOS_SETTINGS_FAQ: + '/user-guide/settings/how-tos/settings-faq', + USER_GUIDE_SETTINGS_OVERVIEW: '/user-guide/settings/overview', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_CALENDAR_VIEW: + '/user-guide/views-pipelines/capabilities/calendar-view', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_FIELDS_AND_COLUMNS: + '/user-guide/views-pipelines/capabilities/fields-and-columns', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_FILTERS_AND_SORTING: + '/user-guide/views-pipelines/capabilities/filters-and-sorting', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_KANBAN_VIEWS: + '/user-guide/views-pipelines/capabilities/kanban-views', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_TABLE_VIEWS: + '/user-guide/views-pipelines/capabilities/table-views', + USER_GUIDE_VIEWS_PIPELINES_CAPABILITIES_VIEW_SETTINGS: + '/user-guide/views-pipelines/capabilities/view-settings', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_CREATE_A_CALENDAR_VIEW_FOR_TASKS_DUE: + '/user-guide/views-pipelines/how-tos/create-a-calendar-view-for-tasks-due', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_CREATE_A_KANBAN_VIEW_FOR_PROJECTS: + '/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_CREATE_A_TABLE_VIEW_WITH_GROUPING: + '/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_RESTRICT_ACCESS_TO_YOUR_VIEW: + '/user-guide/views-pipelines/how-tos/restrict-access-to-your-view', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_SET_UP_A_SALES_PIPELINE: + '/user-guide/views-pipelines/how-tos/set-up-a-sales-pipeline', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_SHOW_EXPECTED_AMOUNT_IN_PIPELINE: + '/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline', + USER_GUIDE_VIEWS_PIPELINES_HOW_TOS_TRACK_TIME_IN_STAGE: + '/user-guide/views-pipelines/how-tos/track-time-in-stage', + USER_GUIDE_VIEWS_PIPELINES_OVERVIEW: '/user-guide/views-pipelines/overview', + USER_GUIDE_WORKFLOWS_CAPABILITIES_SEND_EMAILS_FROM_WORKFLOWS: + '/user-guide/workflows/capabilities/send-emails-from-workflows', + USER_GUIDE_WORKFLOWS_CAPABILITIES_USE_BRANCHES_IN_WORKFLOWS: + '/user-guide/workflows/capabilities/use-branches-in-workflows', + USER_GUIDE_WORKFLOWS_CAPABILITIES_USE_ITERATOR: + '/user-guide/workflows/capabilities/use-iterator', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_ACTIONS: + '/user-guide/workflows/capabilities/workflow-actions', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_BRANCHES: + '/user-guide/workflows/capabilities/workflow-branches', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_CREDITS: + '/user-guide/workflows/capabilities/workflow-credits', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_RUNS: + '/user-guide/workflows/capabilities/workflow-runs', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_TRIGGERS: + '/user-guide/workflows/capabilities/workflow-triggers', + USER_GUIDE_WORKFLOWS_CAPABILITIES_WORKFLOW_VERSIONS: + '/user-guide/workflows/capabilities/workflow-versions', + USER_GUIDE_WORKFLOWS_HOW_TOS_ADVANCED_CONFIGURATIONS_HANDLE_ARRAYS_IN_CODE_ACTIONS: + '/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions', + USER_GUIDE_WORKFLOWS_HOW_TOS_CONNECT_TO_OTHER_TOOLS_BRING_PRODUCT_DATA_IN_TWENTY: + '/user-guide/workflows/how-tos/connect-to-other-tools/bring-product-data-in-twenty', + USER_GUIDE_WORKFLOWS_HOW_TOS_CONNECT_TO_OTHER_TOOLS_BRING_TYPEFORM_SUBMISSIONS_IN_TWENTY: + '/user-guide/workflows/how-tos/connect-to-other-tools/bring-typeform-submissions-in-twenty', + USER_GUIDE_WORKFLOWS_HOW_TOS_CONNECT_TO_OTHER_TOOLS_GENERATE_PDF_FROM_TWENTY: + '/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty', + USER_GUIDE_WORKFLOWS_HOW_TOS_CONNECT_TO_OTHER_TOOLS_GENERATE_QUOTE_OR_INVOICE_FROM_TWENTY: + '/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty', + USER_GUIDE_WORKFLOWS_HOW_TOS_CONNECT_TO_OTHER_TOOLS_SET_UP_A_WEBHOOK_TRIGGER: + '/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_CLOSED_WON_AUTOMATIONS: + '/user-guide/workflows/how-tos/crm-automations/closed-won-automations', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_DETECT_STALE_OPPORTUNITIES: + '/user-guide/workflows/how-tos/crm-automations/detect-stale-opportunities', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_DISPLAY_NUMBER_OF_EMAILS_RECEIVED: + '/user-guide/workflows/how-tos/crm-automations/display-number-of-emails-received', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_DISPLAY_RELATED_RECORD_DATA: + '/user-guide/workflows/how-tos/crm-automations/display-related-record-data', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_FORMULA_FIELDS: + '/user-guide/workflows/how-tos/crm-automations/formula-fields', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_NOTIFY_TEAMMATES_OF_NOTE_TO_REVIEW: + '/user-guide/workflows/how-tos/crm-automations/notify-teammates-of-note-to-review', + USER_GUIDE_WORKFLOWS_HOW_TOS_CRM_AUTOMATIONS_SEND_EMAIL_ALERTS_WITH_TASKS_DUE: + '/user-guide/workflows/how-tos/crm-automations/send-email-alerts-with-tasks-due', + USER_GUIDE_WORKFLOWS_HOW_TOS_NEED_MORE_HELP_PROFESSIONAL_SERVICES: + '/user-guide/workflows/how-tos/need-more-help/professional-services', + USER_GUIDE_WORKFLOWS_HOW_TOS_NEED_MORE_HELP_WORKFLOW_TROUBLESHOOTING: + '/user-guide/workflows/how-tos/need-more-help/workflow-troubleshooting', + USER_GUIDE_WORKFLOWS_HOW_TOS_NEED_MORE_HELP_WORKFLOWS_FAQ: + '/user-guide/workflows/how-tos/need-more-help/workflows-faq', + USER_GUIDE_WORKFLOWS_OVERVIEW: '/user-guide/workflows/overview', +} as const; + +export type DocumentationPath = + (typeof DOCUMENTATION_PATHS)[keyof typeof DOCUMENTATION_PATHS]; diff --git a/packages/twenty-shared/src/constants/DocumentationSupportedLanguages.ts b/packages/twenty-shared/src/constants/DocumentationSupportedLanguages.ts new file mode 100644 index 0000000000..8c74187548 --- /dev/null +++ b/packages/twenty-shared/src/constants/DocumentationSupportedLanguages.ts @@ -0,0 +1,21 @@ +import { DOCUMENTATION_DEFAULT_LANGUAGE } from './DocumentationDefaultLanguage'; + +export const DOCUMENTATION_SUPPORTED_LANGUAGES = [ + DOCUMENTATION_DEFAULT_LANGUAGE, + 'fr', + 'ar', + 'cs', + 'de', + 'es', + 'it', + 'ja', + 'ko', + 'pt', + 'ro', + 'ru', + 'tr', + 'zh', +] as const; + +export type DocumentationSupportedLanguage = + (typeof DOCUMENTATION_SUPPORTED_LANGUAGES)[number]; diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts index 50f07917cb..bb577f02b7 100644 --- a/packages/twenty-shared/src/constants/index.ts +++ b/packages/twenty-shared/src/constants/index.ts @@ -14,6 +14,13 @@ export { CURRENCY_CODE_LABELS } from './CurrencyCodeLabels'; export { DATE_TYPE_FORMAT } from './DateTypeFormat'; export { DEFAULT_NUMBER_OF_GROUPS_LIMIT } from './DefaultNumberOfGroupsLimit'; export { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from './DefaultRelativeDateFilterValue'; +export { DOCUMENTATION_BASE_URL } from './DocumentationBaseUrl'; +export { DOCUMENTATION_DEFAULT_LANGUAGE } from './DocumentationDefaultLanguage'; +export { DOCUMENTATION_DEFAULT_PATH } from './DocumentationDefaultPath'; +export type { DocumentationPath } from './DocumentationPaths'; +export { DOCUMENTATION_PATHS } from './DocumentationPaths'; +export type { DocumentationSupportedLanguage } from './DocumentationSupportedLanguages'; +export { DOCUMENTATION_SUPPORTED_LANGUAGES } from './DocumentationSupportedLanguages'; export { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from './FieldForTotalCountAggregateOperation'; export { MAX_OPTIONS_TO_DISPLAY } from './FieldMetadataMaxOptionsToDisplay'; export { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from './FieldRestrictedAdditionalPermissionsRequired'; diff --git a/packages/twenty-shared/src/utils/validation/isValidLocale.ts b/packages/twenty-shared/src/utils/validation/isValidLocale.ts index 813eb7079a..322f907ef7 100644 --- a/packages/twenty-shared/src/utils/validation/isValidLocale.ts +++ b/packages/twenty-shared/src/utils/validation/isValidLocale.ts @@ -1,4 +1,7 @@ -import { APP_LOCALES, type AppLocale } from '@/translations/constants/AppLocales'; +import { + APP_LOCALES, + type AppLocale, +} from '@/translations/constants/AppLocales'; export const isValidLocale = (value: string | null): value is AppLocale => value !== null && value in APP_LOCALES; diff --git a/yarn.lock b/yarn.lock index 98a962d6b5..69b96fcf1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57322,6 +57322,7 @@ __metadata: resolution: "twenty-docs@workspace:packages/twenty-docs" dependencies: mintlify: "npm:latest" + twenty-shared: "workspace:*" languageName: unknown linkType: soft