14240 extensibility ability to create multiple custom domains for each workspace 2 (#14307)
Adds a public-domain core-module. Reorganize custom-domain files properly
This commit is contained in:
@@ -868,22 +868,6 @@ export type CursorPaging = {
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type CustomDomainRecord = {
|
||||
__typename?: 'CustomDomainRecord';
|
||||
key: Scalars['String'];
|
||||
status: Scalars['String'];
|
||||
type: Scalars['String'];
|
||||
validationType: Scalars['String'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CustomDomainValidRecords = {
|
||||
__typename?: 'CustomDomainValidRecords';
|
||||
customDomain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
records: Array<CustomDomainRecord>;
|
||||
};
|
||||
|
||||
/** Database Event Action */
|
||||
export enum DatabaseEventAction {
|
||||
CREATED = 'CREATED',
|
||||
@@ -961,6 +945,22 @@ export enum DistantTableUpdate {
|
||||
TABLE_DELETED = 'TABLE_DELETED'
|
||||
}
|
||||
|
||||
export type DomainRecord = {
|
||||
__typename?: 'DomainRecord';
|
||||
key: Scalars['String'];
|
||||
status: Scalars['String'];
|
||||
type: Scalars['String'];
|
||||
validationType: Scalars['String'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DomainValidRecords = {
|
||||
__typename?: 'DomainValidRecords';
|
||||
domain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
records: Array<DomainRecord>;
|
||||
};
|
||||
|
||||
export type DuplicateWorkflowVersionStepInput = {
|
||||
stepId: Scalars['String'];
|
||||
workflowVersionId: Scalars['String'];
|
||||
@@ -1410,7 +1410,7 @@ export type Mutation = {
|
||||
assignRoleToAgent: Scalars['Boolean'];
|
||||
assignRoleToApiKey: Scalars['Boolean'];
|
||||
authorizeApp: AuthorizeApp;
|
||||
checkCustomDomainValidRecords?: Maybe<CustomDomainValidRecords>;
|
||||
checkCustomDomainValidRecords?: Maybe<DomainValidRecords>;
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createAgentChatThread: AgentChatThread;
|
||||
@@ -1438,6 +1438,7 @@ export type Mutation = {
|
||||
createPageLayout: PageLayout;
|
||||
createPageLayoutTab: PageLayoutTab;
|
||||
createPageLayoutWidget: PageLayoutWidget;
|
||||
createPublicDomain: PublicDomain;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1462,6 +1463,7 @@ export type Mutation = {
|
||||
deletePageLayout: PageLayout;
|
||||
deletePageLayoutTab: Scalars['Boolean'];
|
||||
deletePageLayoutWidget: PageLayoutWidget;
|
||||
deletePublicDomain: Scalars['Boolean'];
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1734,6 +1736,11 @@ export type MutationCreatePageLayoutWidgetArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePublicDomainArgs = {
|
||||
domain: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1849,6 +1856,11 @@ export type MutationDeletePageLayoutWidgetArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePublicDomainArgs = {
|
||||
domain: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -2593,6 +2605,14 @@ export type PostgresCredentials = {
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type PublicDomain = {
|
||||
__typename?: 'PublicDomain';
|
||||
createdAt: Scalars['DateTime'];
|
||||
domain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
isValidated: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type PublicFeatureFlag = {
|
||||
__typename?: 'PublicFeatureFlag';
|
||||
key: FeatureFlagKey;
|
||||
@@ -5403,7 +5423,7 @@ export type UploadWorkspaceLogoMutation = { __typename?: 'Mutation', uploadWorks
|
||||
export type CheckCustomDomainValidRecordsMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type CheckCustomDomainValidRecordsMutation = { __typename?: 'Mutation', checkCustomDomainValidRecords?: { __typename?: 'CustomDomainValidRecords', id: string, customDomain: string, records: Array<{ __typename?: 'CustomDomainRecord', type: string, key: string, value: string, validationType: string, status: string }> } | null };
|
||||
export type CheckCustomDomainValidRecordsMutation = { __typename?: 'Mutation', checkCustomDomainValidRecords?: { __typename?: 'DomainValidRecords', id: string, domain: string, records: Array<{ __typename?: 'DomainRecord', type: string, key: string, value: string, validationType: string, status: string }> } | null };
|
||||
|
||||
export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
|
||||
inviteHash: Scalars['String'];
|
||||
@@ -12604,7 +12624,7 @@ export const CheckCustomDomainValidRecordsDocument = gql`
|
||||
mutation CheckCustomDomainValidRecords {
|
||||
checkCustomDomainValidRecords {
|
||||
id
|
||||
customDomain
|
||||
domain
|
||||
records {
|
||||
type
|
||||
key
|
||||
|
||||
@@ -832,22 +832,6 @@ export type CursorPaging = {
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type CustomDomainRecord = {
|
||||
__typename?: 'CustomDomainRecord';
|
||||
key: Scalars['String'];
|
||||
status: Scalars['String'];
|
||||
type: Scalars['String'];
|
||||
validationType: Scalars['String'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CustomDomainValidRecords = {
|
||||
__typename?: 'CustomDomainValidRecords';
|
||||
customDomain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
records: Array<CustomDomainRecord>;
|
||||
};
|
||||
|
||||
/** Database Event Action */
|
||||
export enum DatabaseEventAction {
|
||||
CREATED = 'CREATED',
|
||||
@@ -925,6 +909,22 @@ export enum DistantTableUpdate {
|
||||
TABLE_DELETED = 'TABLE_DELETED'
|
||||
}
|
||||
|
||||
export type DomainRecord = {
|
||||
__typename?: 'DomainRecord';
|
||||
key: Scalars['String'];
|
||||
status: Scalars['String'];
|
||||
type: Scalars['String'];
|
||||
validationType: Scalars['String'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DomainValidRecords = {
|
||||
__typename?: 'DomainValidRecords';
|
||||
domain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
records: Array<DomainRecord>;
|
||||
};
|
||||
|
||||
export type DuplicateWorkflowVersionStepInput = {
|
||||
stepId: Scalars['String'];
|
||||
workflowVersionId: Scalars['String'];
|
||||
@@ -1367,7 +1367,7 @@ export type Mutation = {
|
||||
assignRoleToAgent: Scalars['Boolean'];
|
||||
assignRoleToApiKey: Scalars['Boolean'];
|
||||
authorizeApp: AuthorizeApp;
|
||||
checkCustomDomainValidRecords?: Maybe<CustomDomainValidRecords>;
|
||||
checkCustomDomainValidRecords?: Maybe<DomainValidRecords>;
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createAgentChatThread: AgentChatThread;
|
||||
@@ -1394,6 +1394,7 @@ export type Mutation = {
|
||||
createPageLayout: PageLayout;
|
||||
createPageLayoutTab: PageLayoutTab;
|
||||
createPageLayoutWidget: PageLayoutWidget;
|
||||
createPublicDomain: PublicDomain;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1417,6 +1418,7 @@ export type Mutation = {
|
||||
deletePageLayout: PageLayout;
|
||||
deletePageLayoutTab: Scalars['Boolean'];
|
||||
deletePageLayoutWidget: PageLayoutWidget;
|
||||
deletePublicDomain: Scalars['Boolean'];
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1670,6 +1672,11 @@ export type MutationCreatePageLayoutWidgetArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePublicDomainArgs = {
|
||||
domain: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1780,6 +1787,11 @@ export type MutationDeletePageLayoutWidgetArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePublicDomainArgs = {
|
||||
domain: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -2504,6 +2516,14 @@ export type PostgresCredentials = {
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type PublicDomain = {
|
||||
__typename?: 'PublicDomain';
|
||||
createdAt: Scalars['DateTime'];
|
||||
domain: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
isValidated: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type PublicFeatureFlag = {
|
||||
__typename?: 'PublicFeatureFlag';
|
||||
key: FeatureFlagKey;
|
||||
|
||||
+5
-5
@@ -7,8 +7,8 @@ import styled from '@emotion/styled';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import {
|
||||
type CustomDomainRecord,
|
||||
type CustomDomainValidRecords,
|
||||
type DomainRecord,
|
||||
type DomainValidRecords,
|
||||
} from '~/generated/graphql';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
@@ -49,7 +49,7 @@ const StyledButton = styled(Button)`
|
||||
export const SettingsCustomDomainRecords = ({
|
||||
records,
|
||||
}: {
|
||||
records: CustomDomainValidRecords['records'];
|
||||
records: DomainValidRecords['records'];
|
||||
}) => {
|
||||
const { customDomainRecords } = useRecoilValue(customDomainRecordsState);
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
@@ -67,7 +67,7 @@ export const SettingsCustomDomainRecords = ({
|
||||
];
|
||||
|
||||
const defaultValues: { status: string; color: ThemeColor } =
|
||||
currentWorkspace?.customDomain === customDomainRecords?.customDomain
|
||||
currentWorkspace?.customDomain === customDomainRecords?.domain
|
||||
? {
|
||||
status: 'success',
|
||||
color: 'green',
|
||||
@@ -78,7 +78,7 @@ export const SettingsCustomDomainRecords = ({
|
||||
};
|
||||
|
||||
const rows = rowsDefinitions.map<
|
||||
{ name: string; status: string; color: ThemeColor } & CustomDomainRecord
|
||||
{ name: string; status: string; color: ThemeColor } & DomainRecord
|
||||
>((row) => {
|
||||
const record = records.find(
|
||||
({ validationType }) => validationType === row.validationType,
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
import { useCheckCustomDomainValidRecordsMutation } from '~/generated-metadata/graphql';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { customDomainRecordsState } from '@/settings/domains/states/customDomainRecordsState';
|
||||
@@ -9,9 +9,14 @@ export const useCheckCustomDomainValidRecords = () => {
|
||||
useCheckCustomDomainValidRecordsMutation();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const setCustomDomainRecords = useSetRecoilState(customDomainRecordsState);
|
||||
const [{ isLoading }, setCustomDomainRecords] = useRecoilState(
|
||||
customDomainRecordsState,
|
||||
);
|
||||
|
||||
const checkCustomDomainRecords = () => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setCustomDomainRecords((currentState) => ({
|
||||
...currentState,
|
||||
isLoading: true,
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { type CustomDomainValidRecords } from '~/generated/graphql';
|
||||
import { type DomainValidRecords } from '~/generated/graphql';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const customDomainRecordsState = createState<{
|
||||
customDomainRecords: CustomDomainValidRecords | null;
|
||||
customDomainRecords: DomainValidRecords | null;
|
||||
isLoading: boolean;
|
||||
}>({
|
||||
key: 'customDomainRecordsState',
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ export const CHECK_CUSTOM_DOMAIN_VALID_RECORDS = gql`
|
||||
mutation CheckCustomDomainValidRecords {
|
||||
checkCustomDomainValidRecords {
|
||||
id
|
||||
customDomain
|
||||
domain
|
||||
records {
|
||||
type
|
||||
key
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsCustomDomain } from '@/settings/domains/components/SettingsCustomDomain';
|
||||
import { SettingsSubdomain } from '@/settings/domains/components/SettingsSubdomain';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
@@ -21,6 +19,9 @@ import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { SettingsCustomDomain } from '@/settings/domains/components/SettingsCustomDomain';
|
||||
import { SettingsSubdomain } from '@/settings/domains/components/SettingsSubdomain';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID =
|
||||
'subdomain-change-confirmation-modal';
|
||||
@@ -61,6 +62,7 @@ export const SettingsDomain = () => {
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
@@ -88,6 +90,10 @@ export const SettingsDomain = () => {
|
||||
customDomain: string | null,
|
||||
currentWorkspace: CurrentWorkspace,
|
||||
) => {
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
@@ -106,6 +112,7 @@ export const SettingsDomain = () => {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Custom domain updated`,
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
onError: (error: ApolloError) => {
|
||||
if (
|
||||
@@ -120,6 +127,7 @@ export const SettingsDomain = () => {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -128,6 +136,10 @@ export const SettingsDomain = () => {
|
||||
subdomain: string,
|
||||
currentWorkspace: CurrentWorkspace,
|
||||
) => {
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
@@ -148,6 +160,7 @@ export const SettingsDomain = () => {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
onCompleted: async () => {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
@@ -164,6 +177,7 @@ export const SettingsDomain = () => {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Subdomain updated`,
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
|
||||
await redirectToWorkspaceDomain(currentUrl.toString());
|
||||
},
|
||||
@@ -222,7 +236,7 @@ export const SettingsDomain = () => {
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
onCancel={() => navigate(SettingsPath.Domains)}
|
||||
isSaveDisabled={form.formState.isSubmitting}
|
||||
isSaveDisabled={isSubmitting}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user