Reorganize files properly (#14262)

just code reorganization
This commit is contained in:
martmull
2025-09-02 18:56:34 +02:00
committed by GitHub
parent b0c76c799c
commit 7c195b3bfd
13 changed files with 19 additions and 21 deletions
@@ -125,11 +125,17 @@ const SettingsWorkspace = lazy(() =>
);
const SettingsDomains = lazy(() =>
import('~/pages/settings/domains').then((module) => ({
import('~/pages/settings/domains/SettingsDomains').then((module) => ({
default: module.SettingsDomains,
})),
);
const SettingsDomain = lazy(() =>
import('~/pages/settings/domains/SettingsDomain').then((module) => ({
default: module.SettingsDomain,
})),
);
const SettingsApiWebhooks = lazy(() =>
import('~/pages/settings/workspace/SettingsApiWebhooks').then((module) => ({
default: module.SettingsApiWebhooks,
@@ -148,12 +154,6 @@ const SettingsAgentForm = lazy(() =>
})),
);
const SettingsDomain = lazy(() =>
import('~/pages/settings/workspace/SettingsDomain').then((module) => ({
default: module.SettingsDomain,
})),
);
const SettingsWorkspaceMembers = lazy(() =>
import('~/pages/settings/SettingsWorkspaceMembers').then((module) => ({
default: module.SettingsWorkspaceMembers,
@@ -0,0 +1,15 @@
/* @license Enterprise */
import { useEffect } from 'react';
import { useCheckCustomDomainValidRecords } from '@/settings/domains/hooks/useCheckCustomDomainValidRecords';
export const CheckCustomDomainValidRecordsEffect = () => {
const { checkCustomDomainRecords } = useCheckCustomDomainValidRecords();
useEffect(() => {
checkCustomDomainRecords();
// Check custom domain only needs to run once at mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <></>;
};
@@ -0,0 +1,110 @@
/* @license Enterprise */
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { TextInput } from '@/ui/input/components/TextInput';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { Controller, useFormContext } from 'react-hook-form';
import { useRecoilValue } from 'recoil';
import { H2Title, IconReload, IconTrash } from 'twenty-ui/display';
import { Button, ButtonGroup } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { SettingsCustomDomainRecords } from '@/settings/domains/components/SettingsCustomDomainRecords';
import { CheckCustomDomainValidRecordsEffect } from '@/settings/domains/CheckCustomDomainValidRecordsEffect';
import { useCheckCustomDomainValidRecords } from '@/settings/domains/hooks/useCheckCustomDomainValidRecords';
import { customDomainRecordsState } from '@/settings/domains/states/customDomainRecordsState';
const StyledDomainFormWrapper = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledButtonGroup = styled(ButtonGroup)`
& > :not(:first-of-type) > button {
border-left: none;
}
`;
const StyledButton = styled(Button)`
align-self: flex-start;
`;
const StyledRecordsWrapper = styled.div`
margin-top: ${({ theme }) => theme.spacing(2)};
& > :not(:first-of-type) {
margin-top: ${({ theme }) => theme.spacing(4)};
}
`;
export const SettingsCustomDomain = () => {
const { customDomainRecords, isLoading } = useRecoilValue(
customDomainRecordsState,
);
const { checkCustomDomainRecords } = useCheckCustomDomainValidRecords();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { t } = useLingui();
const { control, setValue, trigger } = useFormContext<{
customDomain: string;
}>();
const deleteCustomDomain = () => {
setValue('customDomain', '');
trigger();
};
return (
<Section>
<H2Title
title={t`Custom Domain`}
description={t`Set the name of your custom domain and configure your DNS records.`}
/>
<CheckCustomDomainValidRecordsEffect />
<StyledDomainFormWrapper>
<Controller
name="customDomain"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextInput
value={value}
type="text"
onChange={onChange}
placeholder="crm.yourdomain.com"
error={error?.message}
fullWidth
/>
)}
/>
{currentWorkspace?.customDomain && (
<StyledButtonGroup>
<StyledButton
isLoading={isLoading}
Icon={IconReload}
title={t`Reload`}
variant="primary"
onClick={checkCustomDomainRecords}
type="button"
/>
<StyledButton
Icon={IconTrash}
variant="primary"
onClick={deleteCustomDomain}
/>
</StyledButtonGroup>
)}
</StyledDomainFormWrapper>
{currentWorkspace?.customDomain && (
<StyledRecordsWrapper>
{customDomainRecords && (
<SettingsCustomDomainRecords
records={customDomainRecords.records}
/>
)}
</StyledRecordsWrapper>
)}
</Section>
);
};
@@ -0,0 +1,143 @@
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { Button } from 'twenty-ui/input';
import { useDebouncedCallback } from 'use-debounce';
import {
type CustomDomainRecord,
type CustomDomainValidRecords,
} from '~/generated/graphql';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import { capitalize } from 'twenty-shared/utils';
import { useRecoilValue } from 'recoil';
import { type ThemeColor } from 'twenty-ui/theme';
import { Status } from 'twenty-ui/display';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { customDomainRecordsState } from '@/settings/domains/states/customDomainRecordsState';
const StyledTable = styled(Table)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
const StyledTableCell = styled(TableCell)`
overflow: hidden;
display: block;
padding: 0 ${({ theme }) => theme.spacing(3)} 0 0;
&:first-of-type {
padding-left: 0;
}
&:last-of-type {
padding-right: 0;
}
`;
const StyledButton = styled(Button)`
border: 1px solid ${({ theme }) => theme.border.color.medium};
color: ${({ theme }) => theme.font.color.tertiary};
font-weight: ${({ theme }) => theme.font.weight.regular};
height: ${({ theme }) => theme.spacing(6)};
overflow: hidden;
user-select: text;
width: 100%;
`;
export const SettingsCustomDomainRecords = ({
records,
}: {
records: CustomDomainValidRecords['records'];
}) => {
const { customDomainRecords } = useRecoilValue(customDomainRecordsState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { copyToClipboard } = useCopyToClipboard();
const copyToClipboardDebounced = useDebouncedCallback(
(value: string) => copyToClipboard(value),
200,
);
const rowsDefinitions = [
{ name: 'Domain Setup', validationType: 'redirection' as const },
{ name: 'Secure Connection', validationType: 'ssl' as const },
];
const defaultValues: { status: string; color: ThemeColor } =
currentWorkspace?.customDomain === customDomainRecords?.customDomain
? {
status: 'success',
color: 'green',
}
: {
status: 'loading',
color: 'gray',
};
const rows = rowsDefinitions.map<
{ name: string; status: string; color: ThemeColor } & CustomDomainRecord
>((row) => {
const record = records.find(
({ validationType }) => validationType === row.validationType,
);
if (!record) {
throw new Error(`Record ${row.name} not found`);
}
return {
name: row.name,
color:
record && record.status === 'error'
? 'red'
: record && record.status === 'pending'
? 'yellow'
: defaultValues.color,
...record,
};
});
return (
<StyledTable>
<TableRow gridAutoColumns="30% 16% 38% 16%">
<TableHeader>Name</TableHeader>
<TableHeader>Type</TableHeader>
<TableHeader>Value</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<TableBody>
{rows.map((row) => (
<TableRow gridAutoColumns="30% 16% 38% 16%" key={row.name}>
<StyledTableCell>
<StyledButton
title={row.key}
onClick={() => copyToClipboardDebounced(row.key)}
type="button"
/>
</StyledTableCell>
<StyledTableCell>
<StyledButton
title={row.type.toUpperCase()}
onClick={() => copyToClipboardDebounced(row.type.toUpperCase())}
type="button"
/>
</StyledTableCell>
<StyledTableCell>
<StyledButton
title={row.value}
onClick={() => copyToClipboardDebounced(row.value)}
type="button"
/>
</StyledTableCell>
<StyledTableCell>
<Status color={row.color} text={capitalize(row.status)} />
</StyledTableCell>
</TableRow>
))}
</TableBody>
</StyledTable>
);
};
@@ -0,0 +1,59 @@
import { TextInput } from '@/ui/input/components/TextInput';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { Controller, useFormContext } from 'react-hook-form';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const StyledDomainFormWrapper = styled.div`
align-items: center;
display: flex;
`;
export const SettingsSubdomain = () => {
const domainConfiguration = useRecoilValue(domainConfigurationState);
const { t } = useLingui();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { control } = useFormContext<{
subdomain: string;
}>();
return (
<Section>
<H2Title
title={t`Subdomain`}
description={t`Set the name of your subdomain`}
/>
<StyledDomainFormWrapper>
<Controller
name="subdomain"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<TextInput
value={value}
type="text"
onChange={onChange}
error={error?.message}
disabled={!!currentWorkspace?.customDomain}
rightAdornment={
isDefined(domainConfiguration.frontDomain)
? `.${domainConfiguration.frontDomain}`
: undefined
}
fullWidth
/>
</>
)}
/>
</StyledDomainFormWrapper>
</Section>
);
};
@@ -0,0 +1,36 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { SettingsCard } from '@/settings/components/SettingsCard';
import { SettingsPath } from '@/types/SettingsPath';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconWorld, Status } from 'twenty-ui/display';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsWorkspaceDomainCard = () => {
const { t } = useLingui();
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
if (!isMultiWorkspaceEnabled) {
return null;
}
return (
<UndecoratedLink to={getSettingsPath(SettingsPath.Domain)}>
<SettingsCard
title={t`Customize Domain`}
Icon={<IconWorld />}
Status={
currentWorkspace?.customDomain &&
currentWorkspace?.isCustomDomainEnabled ? (
<Status text={t`Active`} color={'turquoise'} />
) : currentWorkspace?.customDomain ? (
<Status text={t`Inactive`} color={'orange'} />
) : undefined
}
/>
</UndecoratedLink>
);
};
@@ -0,0 +1,42 @@
import { useCheckCustomDomainValidRecordsMutation } from '~/generated-metadata/graphql';
import { useSetRecoilState } 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';
export const useCheckCustomDomainValidRecords = () => {
const [checkCustomDomainValidRecords] =
useCheckCustomDomainValidRecordsMutation();
const { enqueueErrorSnackBar } = useSnackBar();
const setCustomDomainRecords = useSetRecoilState(customDomainRecordsState);
const checkCustomDomainRecords = () => {
setCustomDomainRecords((currentState) => ({
...currentState,
isLoading: true,
}));
checkCustomDomainValidRecords({
onCompleted: (data) => {
setCustomDomainRecords((currentState) => ({
...currentState,
isLoading: false,
...(isDefined(data.checkCustomDomainValidRecords)
? { customDomainRecords: data.checkCustomDomainValidRecords }
: {}),
}));
},
onError: (error) => {
enqueueErrorSnackBar({ apolloError: error });
setCustomDomainRecords((currentState) => ({
...currentState,
isLoading: false,
}));
},
});
};
return {
checkCustomDomainRecords,
};
};
@@ -0,0 +1,10 @@
import { type CustomDomainValidRecords } from '~/generated/graphql';
import { createState } from 'twenty-ui/utilities';
export const customDomainRecordsState = createState<{
customDomainRecords: CustomDomainValidRecords | null;
isLoading: boolean;
}>({
key: 'customDomainRecordsState',
defaultValue: { isLoading: false, customDomainRecords: null },
});
@@ -21,8 +21,9 @@ export enum SettingsPath {
ServerlessFunctionDetail = 'functions/:serverlessFunctionId',
WorkspaceMembersPage = 'members',
Workspace = 'general',
Domain = 'general/domain',
Domains = 'domains',
Domain = 'domains/domain',
NewApprovedAccessDomain = 'domains/approved-access-domain/new',
Releases = 'releases',
AI = 'ai',
AINewAgent = 'ai/new-agent',
@@ -42,7 +43,6 @@ export enum SettingsPath {
IntegrationNewDatabaseConnection = 'integrations/:databaseKey/new',
Security = 'security',
NewSSOIdentityProvider = 'security/sso/new',
NewApprovedAccessDomain = 'security/approved-access-domain/new',
AdminPanel = 'admin-panel',
AdminPanelHealthStatus = 'admin-panel#health-status',