diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 0f2ab51ce1..32241f9645 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2356,6 +2356,7 @@ type PublicDomain { id: UUID! domain: String! isValidated: Boolean! + applicationId: UUID createdAt: DateTime! } @@ -3327,7 +3328,8 @@ type Mutation { updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag! enablePostgresProxy: PostgresCredentials! disablePostgresProxy: PostgresCredentials! - createPublicDomain(domain: String!): PublicDomain! + createPublicDomain(domain: String!, applicationId: String): PublicDomain! + updatePublicDomain(domain: String!, applicationId: String): PublicDomain! deletePublicDomain(domain: String!): Boolean! checkPublicDomainValidRecords(domain: String!): DomainValidRecords createEmailingDomain(domain: String!, driver: EmailingDomainDriver!): EmailingDomain! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 4919e7ef7f..38dec16073 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -2026,6 +2026,7 @@ export interface PublicDomain { id: Scalars['UUID'] domain: Scalars['String'] isValidated: Scalars['Boolean'] + applicationId?: Scalars['UUID'] createdAt: Scalars['DateTime'] __typename: 'PublicDomain' } @@ -2856,6 +2857,7 @@ export interface Mutation { enablePostgresProxy: PostgresCredentials disablePostgresProxy: PostgresCredentials createPublicDomain: PublicDomain + updatePublicDomain: PublicDomain deletePublicDomain: Scalars['Boolean'] checkPublicDomainValidRecords?: DomainValidRecords createEmailingDomain: EmailingDomain @@ -5035,6 +5037,7 @@ export interface PublicDomainGenqlSelection{ id?: boolean | number domain?: boolean | number isValidated?: boolean | number + applicationId?: boolean | number createdAt?: boolean | number __typename?: boolean | number __scalar?: boolean | number @@ -5922,7 +5925,8 @@ export interface MutationGenqlSelection{ updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} }) enablePostgresProxy?: PostgresCredentialsGenqlSelection disablePostgresProxy?: PostgresCredentialsGenqlSelection - createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String']} }) + createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} }) + updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} }) deletePublicDomain?: { __args: {domain: Scalars['String']} } checkPublicDomainValidRecords?: (DomainValidRecordsGenqlSelection & { __args: {domain: Scalars['String']} }) createEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {domain: Scalars['String'], driver: EmailingDomainDriver} }) diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index d715db5293..8ae7417cd7 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -4616,6 +4616,9 @@ export default { "isValidated": [ 6 ], + "applicationId": [ + 3 + ], "createdAt": [ 4 ], @@ -8573,6 +8576,21 @@ export default { "domain": [ 1, "String!" + ], + "applicationId": [ + 1 + ] + } + ], + "updatePublicDomain": [ + 258, + { + "domain": [ + 1, + "String!" + ], + "applicationId": [ + 1 ] } ], diff --git a/packages/twenty-docs/developers/extend/api.mdx b/packages/twenty-docs/developers/extend/api.mdx index 1fcb95f917..e450972f04 100644 --- a/packages/twenty-docs/developers/extend/api.mdx +++ b/packages/twenty-docs/developers/extend/api.mdx @@ -37,7 +37,7 @@ Both are available as REST and GraphQL. GraphQL adds batch upserts and the abili Authorization: Bearer YOUR_API_KEY ``` -Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Members → Roles → Assignment tab** to limit what they can access. diff --git a/packages/twenty-docs/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/developers/extend/capabilities/apis.mdx index e3b9b8e37b..08216f0110 100644 --- a/packages/twenty-docs/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/developers/extend/capabilities/apis.mdx @@ -83,7 +83,7 @@ Your API key grants access to sensitive data. Don't share it with untrusted serv For better security, assign a specific role to limit access: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role to assign 3. Open the **Assignment** tab 4. Under **API Keys**, click **+ Assign to API key** diff --git a/packages/twenty-docs/user-guide/ai/capabilities/permissions-access-control.mdx b/packages/twenty-docs/user-guide/ai/capabilities/permissions-access-control.mdx index afa8063e51..17c3fc4ba7 100644 --- a/packages/twenty-docs/user-guide/ai/capabilities/permissions-access-control.mdx +++ b/packages/twenty-docs/user-guide/ai/capabilities/permissions-access-control.mdx @@ -9,7 +9,7 @@ AI agents respect your existing permission structure. This is particularly impor ## Assign a Role to an AI Agent -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role you want to assign 3. Open the **Assignment** tab 4. Under **AI Agents**, click **+ Assign to AI agent** diff --git a/packages/twenty-docs/user-guide/ai/how-tos/ai-faq.mdx b/packages/twenty-docs/user-guide/ai/how-tos/ai-faq.mdx index e0c03e2fb9..c36d3540f1 100644 --- a/packages/twenty-docs/user-guide/ai/how-tos/ai-faq.mdx +++ b/packages/twenty-docs/user-guide/ai/how-tos/ai-faq.mdx @@ -17,7 +17,7 @@ description: Frequently asked questions about AI features in Twenty. - AI agents will operate under the permission system. You can assign specific roles to AI agents under **Settings → Roles**, giving you full control over what data they can access and what actions they can perform. + AI agents will operate under the permission system. You can assign specific roles to AI agents under **Settings → Members → Roles**, giving you full control over what data they can access and what actions they can perform. diff --git a/packages/twenty-docs/user-guide/ai/overview.mdx b/packages/twenty-docs/user-guide/ai/overview.mdx index 5d7aa93f91..a9ecd46329 100644 --- a/packages/twenty-docs/user-guide/ai/overview.mdx +++ b/packages/twenty-docs/user-guide/ai/overview.mdx @@ -44,7 +44,7 @@ Extend your workflows with AI-powered actions and autonomous agents. AI agents will be managed through the existing permissions system: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Configure which data each AI agent can access 3. Set read/write permissions per object diff --git a/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-other-crms.mdx b/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-other-crms.mdx index 5b261ff33b..42de0b9bf5 100644 --- a/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-other-crms.mdx +++ b/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-other-crms.mdx @@ -204,7 +204,7 @@ After importing data, complete your workspace configuration: - Test each one before relying on it ### Configure Roles and Permissions -- Set up roles in **Settings → Roles** +- Set up roles in **Settings → Members → Roles** - Assign users to appropriate roles ### Connect Email and Calendar diff --git a/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-self-hosted-to-cloud.mdx b/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-self-hosted-to-cloud.mdx index 4694aef5c0..1dbb113a06 100644 --- a/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-self-hosted-to-cloud.mdx +++ b/packages/twenty-docs/user-guide/data-migration/how-tos/migrating-from-self-hosted-to-cloud.mdx @@ -121,7 +121,7 @@ After importing data, manually recreate: - Test each workflow before relying on it ### Roles and Permissions -- Configure roles in **Settings → Roles** +- Configure roles in **Settings → Members → Roles** - Assign users to appropriate roles ### Integrations diff --git a/packages/twenty-docs/user-guide/permissions-access/capabilities/permissions.mdx b/packages/twenty-docs/user-guide/permissions-access/capabilities/permissions.mdx index 217453aee8..3cb8840592 100644 --- a/packages/twenty-docs/user-guide/permissions-access/capabilities/permissions.mdx +++ b/packages/twenty-docs/user-guide/permissions-access/capabilities/permissions.mdx @@ -13,7 +13,7 @@ Twenty's permission system allows you to control access to three main areas: To create a new role: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Under **All Roles**, click on **+ Create Role** 3. Enter a role name 4. In the default **Permissions** tab, [configure permissions](#customize-permissions) @@ -23,7 +23,7 @@ To create a new role: To delete a role: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role you want to remove 3. Open the **Settings** tab, then click **Delete Role** 4. Click **Confirm** in the modal @@ -35,12 +35,12 @@ If a role is deleted, any workspace member assigned to it will be automatically ## Assign Roles to Members ### View Current Assignments -- Go to **Settings → Roles** +- Go to **Settings → Members → Roles** - See all roles and how many members are assigned to each - View which members have which roles ### Assign a Role to a Member -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role you want to assign 3. Open the **Assignment** tab 4. Click **+ Assign to member** @@ -48,7 +48,7 @@ If a role is deleted, any workspace member assigned to it will be automatically 6. Confirm the assignment ### Set Default Role -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. In the **Options** section, find **Default Role** 3. Select which role new members should automatically receive 4. New workspace members will be assigned this role when they join @@ -164,7 +164,7 @@ Beyond workspace members, roles can also be assigned to **API Keys** and **AI Ag ### Assign a Role to an API Key -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role you want to assign 3. Open the **Assignment** tab 4. Under **API Keys**, click **+ Assign to API key** @@ -179,7 +179,7 @@ API keys without an assigned role use default permissions. For tighter security, ### Assign a Role to an AI Agent -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Click on the role you want to assign 3. Open the **Assignment** tab 4. Under **AI Agents**, click **+ Assign to AI agent** diff --git a/packages/twenty-docs/user-guide/permissions-access/how-tos/permissions-faq.mdx b/packages/twenty-docs/user-guide/permissions-access/how-tos/permissions-faq.mdx index ff9a3e55a7..0d5510508a 100644 --- a/packages/twenty-docs/user-guide/permissions-access/how-tos/permissions-faq.mdx +++ b/packages/twenty-docs/user-guide/permissions-access/how-tos/permissions-faq.mdx @@ -19,7 +19,7 @@ Any workspace member assigned to that role will be automatically reassigned to t -Go to **Settings → Roles**, find the **Default Role** option, and select which role new members should automatically receive when they join. +Go to **Settings → Members → Roles**, find the **Default Role** option, and select which role new members should automatically receive when they join. @@ -64,7 +64,7 @@ Row-level permissions will be available on the **Organization** plan by Q1 2026. -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Select the role 3. Navigate to the object containing the field 4. Set the field permission to **See Field** (without Edit Field) diff --git a/packages/twenty-docs/user-guide/settings/capabilities/domains-settings.mdx b/packages/twenty-docs/user-guide/settings/capabilities/domains-settings.mdx index 3909033033..de369d3dc4 100644 --- a/packages/twenty-docs/user-guide/settings/capabilities/domains-settings.mdx +++ b/packages/twenty-docs/user-guide/settings/capabilities/domains-settings.mdx @@ -3,10 +3,12 @@ title: Domain Settings description: Configure workspace domain, approved access domains, and public domains. --- -Configure domain settings under **Settings → Domains**. +Domain settings live in three places, depending on what you're configuring. ## Workspace Domain +Configure under **Settings → General → Workspace Domain**. + Edit your subdomain name or set a custom domain for your workspace. ### Customize Domain @@ -18,6 +20,8 @@ For custom domains, you'll need to configure DNS settings with your domain provi ## Approved Domains +Configure under **Settings → Members → Access**. + Anyone with an email address at these domains is allowed to sign up for this workspace automatically. ### Add Approved Access Domain @@ -33,12 +37,15 @@ This is useful for allowing your entire team to self-register while keeping the ## Public Domains -Provision a complete and secure hosting environment on these domains. +Configure under **Settings → Apps → Developer**. + +Provision a complete and secure hosting environment on these domains. A public domain can be bound to a specific app — when bound, only that app's HTTP-routed logic functions are reachable on the domain. Leave the binding empty to expose all workspace HTTP routes. ### Add Public Domain 1. Click **Add Public Domain** 2. Enter the domain you want to use -3. Configure DNS settings as instructed -4. Verify the domain +3. Optionally bind it to an app +4. Configure DNS settings as instructed +5. Verify the domain SSL certificates are automatically provisioned for public domains. diff --git a/packages/twenty-docs/user-guide/settings/capabilities/member-management.mdx b/packages/twenty-docs/user-guide/settings/capabilities/member-management.mdx index ade88d72e0..2483a24338 100644 --- a/packages/twenty-docs/user-guide/settings/capabilities/member-management.mdx +++ b/packages/twenty-docs/user-guide/settings/capabilities/member-management.mdx @@ -67,7 +67,7 @@ Manage invitations that haven't been accepted: Allow team members to join automatically based on their email domain: -1. Go to **Settings → Domains** +1. Go to **Settings → Members → Access** 2. Add your company domain (e.g., `yourcompany.com`) 3. Anyone with that email domain can join without an invitation diff --git a/packages/twenty-docs/user-guide/settings/how-tos/settings-faq.mdx b/packages/twenty-docs/user-guide/settings/how-tos/settings-faq.mdx index c52ee3a131..f72a05cef8 100644 --- a/packages/twenty-docs/user-guide/settings/how-tos/settings-faq.mdx +++ b/packages/twenty-docs/user-guide/settings/how-tos/settings-faq.mdx @@ -137,7 +137,7 @@ Yes, you can connect multiple email accounts. Go to **Settings → Accounts** an -Yes! Go to **Settings → Domains** and click **Customize Domain**. You have two options: +Yes! Go to **Settings → General → Workspace Domain** and click **Customize Domain**. You have two options: - **Subdomain**: Use a Twenty subdomain like `yourcompany.twenty.com` - **Custom domain**: Use your own domain like `crm.yourcompany.com` (requires DNS configuration) @@ -146,7 +146,7 @@ A subdomain is quick to set up, while a custom domain provides a fully branded e -You can configure approved access domains so team members with company email addresses can automatically join your workspace. Go to **Settings → Domains** and add your company domain (e.g., `yourcompany.com`). +You can configure approved access domains so team members with company email addresses can automatically join your workspace. Go to **Settings → Members → Access** and add your company domain (e.g., `yourcompany.com`). diff --git a/packages/twenty-docs/user-guide/settings/overview.mdx b/packages/twenty-docs/user-guide/settings/overview.mdx index b9ad2faddd..85962decbc 100644 --- a/packages/twenty-docs/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/user-guide/settings/overview.mdx @@ -39,7 +39,7 @@ Add team members to your workspace: 4. Assign appropriate roles -Before inviting your team, check the default role under **Settings → Roles**. New members are automatically assigned this role when they join. +Before inviting your team, check the default role under **Settings → Members → Roles**. New members are automatically assigned this role when they join. ## Workspace Settings Checklist diff --git a/packages/twenty-docs/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline.mdx b/packages/twenty-docs/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline.mdx index 0d4aaf49a2..ff5ae9183f 100644 --- a/packages/twenty-docs/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline.mdx +++ b/packages/twenty-docs/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline.mdx @@ -38,7 +38,7 @@ You need two custom fields on the Opportunities object. If you don't want users manually editing these calculated fields: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Select the role to configure 3. Find the Opportunities object 4. Set **Probability** and **Expected Amount** fields to read-only diff --git a/packages/twenty-docs/user-guide/views-pipelines/how-tos/track-time-in-stage.mdx b/packages/twenty-docs/user-guide/views-pipelines/how-tos/track-time-in-stage.mdx index dc685fd2d8..43034c0c27 100644 --- a/packages/twenty-docs/user-guide/views-pipelines/how-tos/track-time-in-stage.mdx +++ b/packages/twenty-docs/user-guide/views-pipelines/how-tos/track-time-in-stage.mdx @@ -58,7 +58,7 @@ You don't need "Days in" fields for Closed Won and Closed Lost since those are f If you don't want users manually editing these calculated fields: -1. Go to **Settings → Roles** +1. Go to **Settings → Members → Roles** 2. Select the role to configure 3. Find the Opportunities object 4. Set the "Last Entered" and "Days in" fields to read-only diff --git a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx index 70bdc239e5..0459776ef4 100644 --- a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx +++ b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx @@ -261,5 +261,5 @@ AI Agent actions consume workflow credits based on the AI model used. See [Workf -AI agents respect role-based permissions. You can assign specific roles to agents under **Settings → Roles** to control what data they can access. See [Permissions](/user-guide/permissions-access/capabilities/permissions) for details. +AI agents respect role-based permissions. You can assign specific roles to agents under **Settings → Members → Roles** to control what data they can access. See [Permissions](/user-guide/permissions-access/capabilities/permissions) for details. diff --git a/packages/twenty-docs/user-guide/workflows/how-tos/need-more-help/workflows-faq.mdx b/packages/twenty-docs/user-guide/workflows/how-tos/need-more-help/workflows-faq.mdx index 7e71a5c4a4..0270d7cf07 100644 --- a/packages/twenty-docs/user-guide/workflows/how-tos/need-more-help/workflows-faq.mdx +++ b/packages/twenty-docs/user-guide/workflows/how-tos/need-more-help/workflows-faq.mdx @@ -8,7 +8,7 @@ description: Frequently asked questions about workflows in Twenty. This is likely a permissions issue. You need access to workflows to create and activate them. - **Solution**: Contact your workspace administrator to grant you workflow access under **Settings → Roles**. + **Solution**: Contact your workspace administrator to grant you workflow access under **Settings → Members → Roles**. If you don't see the Workflows section at all in your sidebar, this confirms it's a permissions issue. diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 39f9d29ca5..c6de43a5a8 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -2528,6 +2528,7 @@ export type Mutation = { updatePageLayoutWidget: PageLayoutWidget; updatePageLayoutWithTabsAndWidgets: PageLayout; updatePasswordViaResetToken: InvalidatePassword; + updatePublicDomain: PublicDomain; updateSkill: Skill; updateUserEmail: Scalars['Boolean']; updateView: View; @@ -2750,6 +2751,7 @@ export type MutationCreatePageLayoutWidgetArgs = { export type MutationCreatePublicDomainArgs = { + applicationId?: InputMaybe; domain: Scalars['String']; }; @@ -3412,6 +3414,12 @@ export type MutationUpdatePasswordViaResetTokenArgs = { }; +export type MutationUpdatePublicDomainArgs = { + applicationId?: InputMaybe; + domain: Scalars['String']; +}; + + export type MutationUpdateSkillArgs = { input: UpdateSkillInput; }; @@ -4041,6 +4049,7 @@ export type PublicConnectionParametersOutput = { export type PublicDomain = { __typename?: 'PublicDomain'; + applicationId?: Maybe; createdAt: Scalars['DateTime']; domain: Scalars['String']; id: Scalars['UUID']; @@ -7196,10 +7205,11 @@ export type CheckPublicDomainValidRecordsMutation = { __typename?: 'Mutation', c export type CreatePublicDomainMutationVariables = Exact<{ domain: Scalars['String']; + applicationId?: InputMaybe; }>; -export type CreatePublicDomainMutation = { __typename?: 'Mutation', createPublicDomain: { __typename?: 'PublicDomain', id: string, domain: string, isValidated: boolean, createdAt: string } }; +export type CreatePublicDomainMutation = { __typename?: 'Mutation', createPublicDomain: { __typename?: 'PublicDomain', id: string, domain: string, isValidated: boolean, applicationId?: string | null, createdAt: string } }; export type DeletePublicDomainMutationVariables = Exact<{ domain: Scalars['String']; @@ -7208,10 +7218,18 @@ export type DeletePublicDomainMutationVariables = Exact<{ export type DeletePublicDomainMutation = { __typename?: 'Mutation', deletePublicDomain: boolean }; +export type UpdatePublicDomainMutationVariables = Exact<{ + domain: Scalars['String']; + applicationId?: InputMaybe; +}>; + + +export type UpdatePublicDomainMutation = { __typename?: 'Mutation', updatePublicDomain: { __typename?: 'PublicDomain', id: string, domain: string, isValidated: boolean, applicationId?: string | null, createdAt: string } }; + export type FindManyPublicDomainsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyPublicDomainsQuery = { __typename?: 'Query', findManyPublicDomains: Array<{ __typename?: 'PublicDomain', id: string, domain: string, isValidated: boolean, createdAt: string }> }; +export type FindManyPublicDomainsQuery = { __typename?: 'Query', findManyPublicDomains: Array<{ __typename?: 'PublicDomain', id: string, domain: string, isValidated: boolean, applicationId?: string | null, createdAt: string }> }; export type CreateEmailingDomainMutationVariables = Exact<{ domain: Scalars['String']; @@ -8105,9 +8123,10 @@ export const GetApiKeysDocument = {"kind":"Document","definitions":[{"kind":"Ope export const GetWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; export const GetWebhooksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; export const CheckPublicDomainValidRecordsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CheckPublicDomainValidRecords"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"checkPublicDomainValidRecords"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"validationType"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode; -export const CreatePublicDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePublicDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPublicDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; +export const CreatePublicDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePublicDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPublicDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}},{"kind":"Argument","name":{"kind":"Name","value":"applicationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const DeletePublicDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePublicDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePublicDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}}]}]}}]} as unknown as DocumentNode; -export const FindManyPublicDomainsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyPublicDomains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyPublicDomains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; +export const UpdatePublicDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdatePublicDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updatePublicDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}},{"kind":"Argument","name":{"kind":"Name","value":"applicationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; +export const FindManyPublicDomainsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyPublicDomains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyPublicDomains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const CreateEmailingDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateEmailingDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"domain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"driver"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailingDomainDriver"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEmailingDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"domain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"domain"}}},{"kind":"Argument","name":{"kind":"Name","value":"driver"},"value":{"kind":"Variable","name":{"kind":"Name","value":"driver"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"driver"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"verifiedAt"}},{"kind":"Field","name":{"kind":"Name","value":"verificationRecords"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const DeleteEmailingDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteEmailingDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEmailingDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode; export const VerifyEmailingDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"VerifyEmailingDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"verifyEmailingDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"driver"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"verifiedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 29f43a47be..e6f560d102 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -136,12 +136,6 @@ const SettingsWorkspaceEmailGroupChannelDetail = lazy(() => })), ); -const SettingsDomains = lazy(() => - import('~/pages/settings/domains/SettingsDomains').then((module) => ({ - default: module.SettingsDomains, - })), -); - const SettingsSubdomainPage = lazy(() => import('~/pages/settings/domains/SettingsSubdomainPage').then((module) => ({ default: module.SettingsSubdomainPage, @@ -549,32 +543,30 @@ const SettingsUpdates = lazy(() => })), ); -const SettingsRoles = lazy(() => - import('~/pages/settings/roles/SettingsRoles').then((module) => ({ - default: module.SettingsRoles, - })), -); - const SettingsRoleCreate = lazy(() => - import('~/pages/settings/roles/SettingsRoleCreate').then((module) => ({ - default: module.SettingsRoleCreate, - })), + import('~/pages/settings/members/roles/SettingsRoleCreate').then( + (module) => ({ + default: module.SettingsRoleCreate, + }), + ), ); const SettingsRoleEdit = lazy(() => - import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({ + import('~/pages/settings/members/roles/SettingsRoleEdit').then((module) => ({ default: module.SettingsRoleEdit, })), ); const SettingsRoleObjectLevel = lazy(() => - import('~/pages/settings/roles/SettingsRoleObjectLevel').then((module) => ({ - default: module.SettingsRoleObjectLevel, - })), + import('~/pages/settings/members/roles/SettingsRoleObjectLevel').then( + (module) => ({ + default: module.SettingsRoleObjectLevel, + }), + ), ); const SettingsRoleAddObjectLevel = lazy(() => - import('~/pages/settings/roles/SettingsRoleAddObjectLevel').then( + import('~/pages/settings/members/roles/SettingsRoleAddObjectLevel').then( (module) => ({ default: module.SettingsRoleAddObjectLevel, }), @@ -644,7 +636,6 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.EmailGroupChannelDetail} element={} /> - } /> } @@ -763,7 +754,15 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( /> } > - } /> + + } + /> } /> { CreatePublicDomainDocument, ); + const [updatePublicDomain] = useMutation(UpdatePublicDomainDocument); + const [newPublicDomain, setNewPublicDomain] = useState( selectedPublicDomain?.domain ?? '', ); + // Holds the chosen application before the public domain is created. + // Once selectedPublicDomain exists, the dropdown reads from it directly. + const [draftApplicationId, setDraftApplicationId] = useState( + null, + ); + + const selectedApplicationId = isDefined(selectedPublicDomain) + ? (selectedPublicDomain.applicationId ?? null) + : draftApplicationId; + const [newPublicDomainError, setNewPublicDomainError] = useState< string | undefined >(undefined); @@ -73,6 +88,20 @@ export const SettingPublicDomain = () => { FindManyPublicDomainsDocument, ); + const { data: applicationsData } = useQuery(FindManyApplicationsDocument); + + const applicationPinnedOption: SelectOption = { + value: null, + label: t`Workspace (all apps)`, + }; + + const applicationOptions: SelectOption[] = ( + applicationsData?.findManyApplications ?? [] + ).map((application) => ({ + value: application.id, + label: application.name, + })); + const [deletePublicDomain] = useMutation(DeletePublicDomainDocument); const { isLoading, publicDomainRecords, checkPublicDomainRecords } = @@ -89,7 +118,7 @@ export const SettingPublicDomain = () => { enqueueSuccessSnackBar({ message: t`Public domain successfully deleted`, }); - navigate(SettingsPath.Domains); + navigate(SettingsPath.Applications); refetchPublicDomains(); }, onError: (error) => @@ -116,7 +145,10 @@ export const SettingPublicDomain = () => { setNewPublicDomainError(undefined); await createPublicDomain({ - variables: { domain: newPublicDomain }, + variables: { + domain: newPublicDomain, + applicationId: draftApplicationId, + }, onCompleted: (data) => { setSelectedPublicDomain(data.createPublicDomain); enqueueSuccessSnackBar({ @@ -132,6 +164,35 @@ export const SettingPublicDomain = () => { }); }; + const onApplicationChange = async (nextApplicationId: string | null) => { + if (!isDefined(selectedPublicDomain)) { + setDraftApplicationId(nextApplicationId); + return; + } + + if (nextApplicationId === (selectedPublicDomain.applicationId ?? null)) { + return; + } + + await updatePublicDomain({ + variables: { + domain: selectedPublicDomain.domain, + applicationId: nextApplicationId, + }, + onCompleted: (data) => { + setSelectedPublicDomain(data.updatePublicDomain); + enqueueSuccessSnackBar({ + message: t`Public domain updated successfully`, + }); + refetchPublicDomains(); + }, + onError: (error) => + enqueueErrorSnackBar({ + apolloError: error, + }), + }); + }; + return ( { href: getSettingsPath(SettingsPath.Workspace), }, { - children: Domains, - href: getSettingsPath(SettingsPath.Domains), + children: Apps, + href: getSettingsPath(SettingsPath.Applications), }, { children: Public Domain }, ]} actionButton={ navigate(SettingsPath.Domains)} + onCancel={() => navigate(SettingsPath.Applications)} isSaveDisabled={loading || isDefined(selectedPublicDomain)} onSave={onCreate} /> @@ -209,6 +270,21 @@ export const SettingPublicDomain = () => { )} +
+ +