feat(settings): move email handles and emailing domains to dedicated Email page (#21008)
## Summary Both **Email Handles** and **Emailing Domains** were rendered on the General workspace settings page, but they're workspace-level *email infrastructure* (inbound shared addresses + outbound sender authentication) and don't belong with the workspace name, picture, and domain config. - New `SettingsWorkspaceEmail` page at `/settings/email` - Nav item under **Workspace**, hidden when `IS_EMAIL_GROUP_ENABLED` is off (and gated by `WORKSPACE` permission) - Related sub-routes (`email-group/:messageChannelId`, `emailing-domain/:domainId`, etc.) moved from `general/` to `email/` so the URL space stays consistent with the page - General page now only contains name, picture, workspace domain, and the delete-workspace section No behavior changes to the underlying section components — they're imported as-is into the new page. ## Test plan - [ ] With `IS_EMAIL_GROUP_ENABLED` enabled: **Email** appears in the Workspace nav and the page renders both sections - [ ] With the flag disabled: **Email** is hidden from nav; navigating to `/settings/email` directly renders nothing - [ ] General page no longer shows Email Handles / Emailing Domains - [ ] Clicking a shared inbox row navigates to `/settings/email/email-group/:id` (was `general/...`) - [ ] "Add emailing domain" navigates to `/settings/email/emailing-domain/new` ## Notes - Pre-existing `twenty-front` typecheck error in `FrontComponentRendererProvider.tsx` (React types mismatch between sibling packages) reproduces on `main` and is unrelated to this PR.
This commit is contained in:
@@ -128,6 +128,12 @@ const SettingsWorkspace = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsWorkspaceEmail = lazy(() =>
|
||||
import('~/pages/settings/SettingsWorkspaceEmail').then((module) => ({
|
||||
default: module.SettingsWorkspaceEmail,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsWorkspaceEmailGroupChannelDetail = lazy(() =>
|
||||
import('~/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail').then(
|
||||
(module) => ({
|
||||
@@ -636,6 +642,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
|
||||
}
|
||||
>
|
||||
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
|
||||
<Route
|
||||
path={SettingsPath.WorkspaceEmail}
|
||||
element={<SettingsWorkspaceEmail />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.NewEmailGroupChannel}
|
||||
element={<SettingsNewEmailGroupChannel />}
|
||||
|
||||
+3
-3
@@ -53,8 +53,8 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`General`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
children: t`Email`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
},
|
||||
{ children: t`New Email Handle` },
|
||||
]}
|
||||
@@ -63,7 +63,7 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={loading}
|
||||
isLoading={loading}
|
||||
onCancel={() => navigate(SettingsPath.Workspace)}
|
||||
onCancel={() => navigate(SettingsPath.WorkspaceEmail)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type NavigationDrawerItemModifier,
|
||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
@@ -73,6 +74,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
isNonEmptyString(supportChat.supportFrontChatId);
|
||||
|
||||
const permissionMap = usePermissionFlagMap();
|
||||
const isEmailGroupFeatureEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t`User`,
|
||||
@@ -120,6 +124,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconSettings,
|
||||
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Email`,
|
||||
path: SettingsPath.WorkspaceEmail,
|
||||
Icon: IconMail,
|
||||
isHidden:
|
||||
!isEmailGroupFeatureEnabled ||
|
||||
!permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Data model`,
|
||||
path: SettingsPath.Objects,
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { isEmailGroupEnabledState } from '@/client-config/states/isEmailGroupEnabledState';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard';
|
||||
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
|
||||
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
|
||||
import { SettingsWorkspaceEmailingDomainsSection } from '@/settings/workspace/components/SettingsWorkspaceEmailingDomainsSection';
|
||||
import { NameField } from '@/settings/workspace/components/NameField';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
@@ -24,14 +20,6 @@ export const SettingsWorkspace = () => {
|
||||
isMultiWorkspaceEnabledState,
|
||||
);
|
||||
|
||||
const isEmailGroupEnabled = useAtomStateValue(isEmailGroupEnabledState);
|
||||
const isEmailGroupFeatureEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
);
|
||||
const showEmailGroupSection =
|
||||
isEmailGroupEnabled && isEmailGroupFeatureEnabled;
|
||||
const showEmailingDomainsSection = isEmailGroupFeatureEnabled;
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`General`}
|
||||
@@ -61,10 +49,6 @@ export const SettingsWorkspace = () => {
|
||||
<SettingsWorkspaceDomainCard />
|
||||
</Section>
|
||||
)}
|
||||
{showEmailGroupSection && <SettingsWorkspaceEmailGroupSection />}
|
||||
{showEmailingDomainsSection && (
|
||||
<SettingsWorkspaceEmailingDomainsSection />
|
||||
)}
|
||||
<Section>
|
||||
<DeleteWorkspace />
|
||||
</Section>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { isEmailGroupEnabledState } from '@/client-config/states/isEmailGroupEnabledState';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
|
||||
import { SettingsWorkspaceEmailingDomainsSection } from '@/settings/workspace/components/SettingsWorkspaceEmailingDomainsSection';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
|
||||
export const SettingsWorkspaceEmail = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const isEmailGroupEnabled = useAtomStateValue(isEmailGroupEnabledState);
|
||||
const isEmailGroupFeatureEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
);
|
||||
|
||||
if (!isEmailGroupFeatureEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showEmailGroupSection = isEmailGroupEnabled;
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Email`}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: t`Email` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
{showEmailGroupSection && <SettingsWorkspaceEmailGroupSection />}
|
||||
<SettingsWorkspaceEmailingDomainsSection />
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -64,7 +64,7 @@ export const SettingsEmailingDomainDetail = () => {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Emailing domain deleted successfully`,
|
||||
});
|
||||
navigateSettings(SettingsPath.Workspace);
|
||||
navigateSettings(SettingsPath.WorkspaceEmail);
|
||||
} catch (deleteError) {
|
||||
enqueueErrorSnackBar({
|
||||
...(CombinedGraphQLErrors.is(deleteError)
|
||||
@@ -83,8 +83,8 @@ export const SettingsEmailingDomainDetail = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>Emailing Domains</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
children: <Trans>Email</Trans>,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
},
|
||||
{ children: emailingDomain.domain },
|
||||
]}
|
||||
|
||||
+3
-3
@@ -114,7 +114,7 @@ export const SettingsNewEmailingDomain = () => {
|
||||
title={t`New Emailing Domain`}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
onCancel={() => navigate(SettingsPath.Workspace)}
|
||||
onCancel={() => navigate(SettingsPath.WorkspaceEmail)}
|
||||
onSave={handleSave}
|
||||
isSaveDisabled={!canSave}
|
||||
/>
|
||||
@@ -125,8 +125,8 @@ export const SettingsNewEmailingDomain = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>Emailing Domains</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
children: <Trans>Email</Trans>,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
},
|
||||
{ children: <Trans>New Emailing Domain</Trans> },
|
||||
]}
|
||||
|
||||
+3
-3
@@ -76,7 +76,7 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteEmailGroupChannel(channel.id);
|
||||
navigateSettings(SettingsPath.Workspace);
|
||||
navigateSettings(SettingsPath.WorkspaceEmail);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to delete email handle.`,
|
||||
@@ -93,8 +93,8 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`General`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
children: t`Email`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
},
|
||||
{ children: sourceHandle },
|
||||
]}
|
||||
|
||||
@@ -26,12 +26,13 @@ export enum SettingsPath {
|
||||
Workspace = 'general',
|
||||
Subdomain = 'general/subdomain',
|
||||
CustomDomain = 'general/custom-domain',
|
||||
EmailGroupChannelDetail = 'general/email-group/:messageChannelId',
|
||||
NewEmailGroupChannel = 'general/new-email-group',
|
||||
WorkspaceEmail = 'email',
|
||||
EmailGroupChannelDetail = 'email/email-group/:messageChannelId',
|
||||
NewEmailGroupChannel = 'email/new-email-group',
|
||||
PublicDomain = 'applications/public-domain',
|
||||
NewApprovedAccessDomain = 'members/approved-access-domain/new',
|
||||
NewEmailingDomain = 'general/emailing-domain/new',
|
||||
EmailingDomainDetail = 'general/emailing-domain/:domainId',
|
||||
NewEmailingDomain = 'email/emailing-domain/new',
|
||||
EmailingDomainDetail = 'email/emailing-domain/:domainId',
|
||||
Updates = 'updates',
|
||||
AI = 'ai',
|
||||
AiUsageUserDetail = 'ai/usage/user/:userWorkspaceId',
|
||||
|
||||
Reference in New Issue
Block a user