Revamp settings navigation (#14174)
Menu was getting too long so I'm removing the developer section and merging API+Webhook together (in the past we had made a split partially because it was in the advanced mode), I'm also merging Lab and Releases. I'm moving Approved Domains and Workspace Domain to a new dedicated page, which will also hold email domains by end of year, because it makes sense to do all those at once (involve DNS changes).
This commit is contained in:
@@ -6,14 +6,6 @@ import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLo
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { PermissionFlagType } from '~/generated/graphql';
|
||||
|
||||
const SettingsApiKeys = lazy(() =>
|
||||
import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
|
||||
(module) => ({
|
||||
default: module.SettingsApiKeys,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const SettingsGraphQLPlayground = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/developers/playground/SettingsGraphQLPlayground'
|
||||
@@ -30,14 +22,6 @@ const SettingsRestPlayground = lazy(() =>
|
||||
),
|
||||
);
|
||||
|
||||
const SettingsWebhooks = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/developers/webhooks/components/SettingsWebhooks'
|
||||
).then((module) => ({
|
||||
default: module.SettingsWebhooks,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAccountsCalendars = lazy(() =>
|
||||
import('~/pages/settings/accounts/SettingsAccountsCalendars').then(
|
||||
(module) => ({
|
||||
@@ -112,12 +96,6 @@ const SettingsDevelopersApiKeysNew = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const Releases = lazy(() =>
|
||||
import('~/pages/settings/Releases').then((module) => ({
|
||||
default: module.Releases,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsServerlessFunctions = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/serverless-functions/SettingsServerlessFunctions'
|
||||
@@ -146,6 +124,18 @@ const SettingsWorkspace = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsDomains = lazy(() =>
|
||||
import('~/pages/settings/domains').then((module) => ({
|
||||
default: module.SettingsDomains,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsApiWebhooks = lazy(() =>
|
||||
import('~/pages/settings/workspace/SettingsApiWebhooks').then((module) => ({
|
||||
default: module.SettingsApiWebhooks,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAI = lazy(() =>
|
||||
import('~/pages/settings/ai/SettingsAI').then((module) => ({
|
||||
default: module.SettingsAI,
|
||||
@@ -347,9 +337,9 @@ const SettingsAdminConfigVariableDetails = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsLab = lazy(() =>
|
||||
import('~/pages/settings/lab/SettingsLab').then((module) => ({
|
||||
default: module.SettingsLab,
|
||||
const SettingsReleases = lazy(() =>
|
||||
import('~/pages/settings/releases/SettingsReleases').then((module) => ({
|
||||
default: module.SettingsReleases,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -428,6 +418,11 @@ export const SettingsRoutes = ({
|
||||
}
|
||||
>
|
||||
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
|
||||
<Route path={SettingsPath.Domains} element={<SettingsDomains />} />
|
||||
<Route
|
||||
path={SettingsPath.ApiWebhooks}
|
||||
element={<SettingsApiWebhooks />}
|
||||
/>
|
||||
<Route path={SettingsPath.AI} element={<SettingsAI />} />
|
||||
<Route
|
||||
path={SettingsPath.AINewAgent}
|
||||
@@ -511,8 +506,6 @@ export const SettingsRoutes = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Route path={SettingsPath.APIs} element={<SettingsApiKeys />} />
|
||||
<Route path={SettingsPath.Webhooks} element={<SettingsWebhooks />} />
|
||||
<Route
|
||||
path={`${SettingsPath.GraphQLPlayground}`}
|
||||
element={<SettingsGraphQLPlayground />}
|
||||
@@ -578,7 +571,7 @@ export const SettingsRoutes = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route path={SettingsPath.Releases} element={<Releases />} />
|
||||
|
||||
<Route
|
||||
element={
|
||||
<SettingsProtectedRouteWrapper
|
||||
@@ -622,7 +615,7 @@ export const SettingsRoutes = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Route path={SettingsPath.Lab} element={<SettingsLab />} />
|
||||
<Route path={SettingsPath.Releases} element={<SettingsReleases />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
+3
-3
@@ -131,8 +131,8 @@ export const SettingsDevelopersWebhookForm = ({
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`Webhooks`,
|
||||
href: getSettingsPath(SettingsPath.Webhooks),
|
||||
children: t`APIs & Webhooks`,
|
||||
href: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
},
|
||||
{ children: isCreationMode ? t`New` : getTitle() },
|
||||
]}
|
||||
@@ -140,7 +140,7 @@ export const SettingsDevelopersWebhookForm = ({
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={formConfig.formState.isSubmitting}
|
||||
onCancel={() => navigate(SettingsPath.Webhooks)}
|
||||
onCancel={() => navigate(SettingsPath.ApiWebhooks)}
|
||||
onSave={formConfig.handleSubmit(handleSave)}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
});
|
||||
|
||||
navigate(
|
||||
createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.Webhooks,
|
||||
createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.ApiWebhooks,
|
||||
createdWebhook ? { webhookId: createdWebhook.id } : undefined,
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -188,7 +188,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
message: t`Webhook deleted successfully`,
|
||||
});
|
||||
|
||||
navigate(SettingsPath.Webhooks);
|
||||
navigate(SettingsPath.ApiWebhooks);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
|
||||
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
|
||||
import { type NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
type IconComponent,
|
||||
IconCurrencyDollar,
|
||||
IconDoorEnter,
|
||||
IconFlask,
|
||||
IconFunction,
|
||||
IconHierarchy2,
|
||||
IconKey,
|
||||
@@ -31,7 +29,7 @@ import {
|
||||
IconSparkles,
|
||||
IconUserCircle,
|
||||
IconUsers,
|
||||
IconWebhook,
|
||||
IconWorld,
|
||||
} from 'twenty-ui/display';
|
||||
import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql';
|
||||
|
||||
@@ -65,7 +63,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
const isAdminEnabled =
|
||||
(currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ??
|
||||
false;
|
||||
const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState);
|
||||
const isAIEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
|
||||
const isPageLayoutEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_PAGE_LAYOUT_ENABLED,
|
||||
@@ -116,6 +113,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconSettings,
|
||||
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Data model`,
|
||||
path: SettingsPath.Objects,
|
||||
Icon: IconHierarchy2,
|
||||
isHidden: !permissionMap[PermissionFlagType.DATA_MODEL],
|
||||
},
|
||||
{
|
||||
label: t`Members`,
|
||||
path: SettingsPath.WorkspaceMembersPage,
|
||||
@@ -128,6 +131,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconLock,
|
||||
isHidden: !permissionMap[PermissionFlagType.ROLES],
|
||||
},
|
||||
{
|
||||
label: t`Domains`,
|
||||
path: SettingsPath.Domains,
|
||||
Icon: IconWorld,
|
||||
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Billing`,
|
||||
path: SettingsPath.Billing,
|
||||
@@ -136,13 +145,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
!isBillingEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Data model`,
|
||||
path: SettingsPath.Objects,
|
||||
Icon: IconHierarchy2,
|
||||
isHidden: !permissionMap[PermissionFlagType.DATA_MODEL],
|
||||
},
|
||||
{
|
||||
label: t`Page Layout`,
|
||||
label: t`Layout`,
|
||||
path: SettingsPath.PageLayoutEdition,
|
||||
Icon: IconLayout,
|
||||
isHidden:
|
||||
@@ -150,6 +153,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
!permissionMap[PermissionFlagType.WORKSPACE],
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
label: t`APIs & Webhooks`,
|
||||
path: SettingsPath.ApiWebhooks,
|
||||
Icon: IconApi,
|
||||
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Integrations`,
|
||||
path: SettingsPath.Integrations,
|
||||
@@ -164,33 +173,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
!isAIEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
label: t`Security`,
|
||||
path: SettingsPath.Security,
|
||||
Icon: IconKey,
|
||||
isAdvanced: true,
|
||||
isHidden: !permissionMap[PermissionFlagType.SECURITY],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t`Developers`,
|
||||
isAdvanced: false,
|
||||
items: [
|
||||
{
|
||||
label: t`APIs`,
|
||||
path: SettingsPath.APIs,
|
||||
Icon: IconApi,
|
||||
isAdvanced: false,
|
||||
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Webhooks`,
|
||||
path: SettingsPath.Webhooks,
|
||||
Icon: IconWebhook,
|
||||
isAdvanced: false,
|
||||
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`Functions`,
|
||||
path: SettingsPath.ServerlessFunctions,
|
||||
@@ -198,6 +180,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
isHidden: !isFunctionSettingsEnabled,
|
||||
isAdvanced: true,
|
||||
},
|
||||
{
|
||||
label: t`Security`,
|
||||
path: SettingsPath.Security,
|
||||
Icon: IconKey,
|
||||
isAdvanced: true,
|
||||
isHidden: !permissionMap[PermissionFlagType.SECURITY],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -209,18 +198,11 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconServer,
|
||||
isHidden: !isAdminEnabled,
|
||||
},
|
||||
{
|
||||
label: t`Lab`,
|
||||
path: SettingsPath.Lab,
|
||||
Icon: IconFlask,
|
||||
isHidden:
|
||||
!labPublicFeatureFlags.length ||
|
||||
!permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Releases`,
|
||||
path: SettingsPath.Releases,
|
||||
Icon: IconRocket,
|
||||
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
},
|
||||
{
|
||||
label: t`Logout`,
|
||||
|
||||
@@ -22,14 +22,18 @@ export enum SettingsPath {
|
||||
WorkspaceMembersPage = 'members',
|
||||
Workspace = 'general',
|
||||
Domain = 'general/domain',
|
||||
Domains = 'domains',
|
||||
Releases = 'releases',
|
||||
AI = 'ai',
|
||||
AINewAgent = 'ai/new-agent',
|
||||
AIAgentDetail = 'ai/agents/:agentId',
|
||||
APIs = 'apis',
|
||||
ApiWebhooks = 'api-webhooks',
|
||||
RestPlayground = 'playground/rest/:schema',
|
||||
GraphQLPlayground = 'playground/graphql/:schema',
|
||||
NewApiKey = 'apis/new',
|
||||
ApiKeyDetail = 'apis/:apiKeyId',
|
||||
NewApiKey = 'api-webhooks/apis/new',
|
||||
ApiKeyDetail = 'api-webhooks/apis/:apiKeyId',
|
||||
NewWebhook = 'api-webhooks/webhooks/new',
|
||||
WebhookDetail = 'api-webhooks/webhooks/:webhookId',
|
||||
Integrations = 'integrations',
|
||||
IntegrationMCP = 'integrations/mcp',
|
||||
IntegrationDatabase = 'integrations/:databaseKey',
|
||||
@@ -39,15 +43,12 @@ export enum SettingsPath {
|
||||
Security = 'security',
|
||||
NewSSOIdentityProvider = 'security/sso/new',
|
||||
NewApprovedAccessDomain = 'security/approved-access-domain/new',
|
||||
Webhooks = 'webhooks',
|
||||
NewWebhook = 'webhooks/new',
|
||||
WebhookDetail = 'webhooks/:webhookId',
|
||||
Releases = 'releases',
|
||||
|
||||
AdminPanel = 'admin-panel',
|
||||
AdminPanelHealthStatus = 'admin-panel#health-status',
|
||||
AdminPanelIndicatorHealthStatus = 'admin-panel/health-status/:indicatorId',
|
||||
AdminPanelConfigVariableDetails = 'admin-panel/config-variables/:variableName',
|
||||
Lab = 'lab',
|
||||
|
||||
Roles = 'roles',
|
||||
RoleCreate = 'roles/create',
|
||||
RoleDetail = 'roles/:roleId',
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
|
||||
import { NameField } from '@/settings/workspace/components/NameField';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { H2Title, IconWorld, Status } from 'twenty-ui/display';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const SettingsWorkspace = () => {
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
const { t } = useLingui();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`General`}
|
||||
@@ -39,30 +32,7 @@ export const SettingsWorkspace = () => {
|
||||
<H2Title title={t`Name`} description={t`Name of your workspace`} />
|
||||
<NameField />
|
||||
</Section>
|
||||
{isMultiWorkspaceEnabled && (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Domain`}
|
||||
description={t`Edit your subdomain name or set a custom domain.`}
|
||||
/>
|
||||
<UndecoratedLink to={getSettingsPath(SettingsPath.Domain)}>
|
||||
<SettingsCard
|
||||
title={t`Customize Domain`}
|
||||
Icon={<IconWorld />}
|
||||
Status={
|
||||
currentWorkspace?.customDomain &&
|
||||
currentWorkspace?.isCustomDomainEnabled ? (
|
||||
<Status text={'Active'} color={'turquoise'} />
|
||||
) : currentWorkspace?.customDomain ? (
|
||||
<Status text={'Inactive'} color={'orange'} />
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</Section>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Section>
|
||||
<DeleteWorkspace />
|
||||
</Section>
|
||||
|
||||
+3
-3
@@ -143,7 +143,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
},
|
||||
});
|
||||
if (redirect) {
|
||||
navigate(SettingsPath.APIs);
|
||||
navigate(SettingsPath.ApiWebhooks);
|
||||
}
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Error deleting api key.` });
|
||||
@@ -242,8 +242,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`APIs`,
|
||||
href: getSettingsPath(SettingsPath.APIs),
|
||||
children: t`APIs & Webhooks`,
|
||||
href: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
},
|
||||
{ children: apiKey?.name },
|
||||
]}
|
||||
|
||||
+3
-3
@@ -139,8 +139,8 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: t`APIs`,
|
||||
href: getSettingsPath(SettingsPath.APIs),
|
||||
children: t`APIs & Webhooks`,
|
||||
href: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
},
|
||||
{ children: t`New Key` },
|
||||
]}
|
||||
@@ -148,7 +148,7 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
onCancel={() => {
|
||||
navigateSettings(SettingsPath.APIs);
|
||||
navigateSettings(SettingsPath.ApiWebhooks);
|
||||
}}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ export const SettingsGraphQLPlayground = () => {
|
||||
: PlaygroundSchemas.CORE;
|
||||
|
||||
const handleExitFullScreen = () => {
|
||||
navigateSettings(SettingsPath.APIs);
|
||||
navigateSettings(SettingsPath.ApiWebhooks);
|
||||
};
|
||||
|
||||
const handleOnError = () => {
|
||||
@@ -35,8 +35,8 @@ export const SettingsGraphQLPlayground = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>APIs</Trans>,
|
||||
href: getSettingsPath(SettingsPath.APIs),
|
||||
children: <Trans>APIs & Webhooks</Trans>,
|
||||
href: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
},
|
||||
{ children: <Trans>GraphQL API Playground</Trans> },
|
||||
]}
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ export const SettingsRestPlayground = () => {
|
||||
}>();
|
||||
|
||||
const handleExitFullScreen = () => {
|
||||
navigateSettings(SettingsPath.APIs);
|
||||
navigateSettings(SettingsPath.ApiWebhooks);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -26,15 +26,15 @@ export const SettingsRestPlayground = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>APIs</Trans>,
|
||||
href: getSettingsPath(SettingsPath.APIs),
|
||||
children: <Trans>APIs & Webhooks</Trans>,
|
||||
href: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
},
|
||||
{ children: <Trans>REST</Trans> },
|
||||
]}
|
||||
>
|
||||
<RestPlayground
|
||||
schema={schema}
|
||||
onError={() => navigateSettings(SettingsPath.APIs)}
|
||||
onError={() => navigateSettings(SettingsPath.ApiWebhooks)}
|
||||
/>
|
||||
</FullScreenContainer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { SettingsWorkspaceDomainCard } from './components';
|
||||
|
||||
const StyledMainContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(10)};
|
||||
min-height: 200px;
|
||||
`;
|
||||
|
||||
const StyledSection = styled(Section)`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export const SettingsDomains = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Domains`}
|
||||
links={[
|
||||
{
|
||||
children: <Trans>Workspace</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: <Trans>Domains</Trans> },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<StyledMainContent>
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`Workspace Domain`}
|
||||
description={t`Edit your subdomain name or set a custom domain.`}
|
||||
/>
|
||||
<SettingsWorkspaceDomainCard />
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`Approved Domains`}
|
||||
description={t`Anyone with an email address at these domains is allowed to sign up for this workspace.`}
|
||||
/>
|
||||
<SettingsApprovedAccessDomainsListCard />
|
||||
</StyledSection>
|
||||
</StyledMainContent>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
+36
@@ -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 @@
|
||||
export { SettingsWorkspaceDomainCard } from './SettingsWorkspaceDomainCard';
|
||||
@@ -0,0 +1 @@
|
||||
export { SettingsDomains } from './SettingsDomains';
|
||||
@@ -1,25 +0,0 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsLab = () => {
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Lab`}
|
||||
links={[
|
||||
{
|
||||
children: t`Other`,
|
||||
href: getSettingsPath(SettingsPath.Lab),
|
||||
},
|
||||
{ children: t`Lab` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsLabContent />
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconFlask, IconRocket } from 'twenty-ui/display';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { SettingsReleasesTabContent } from './components';
|
||||
import { SETTINGS_RELEASES_TABS, SETTINGS_RELEASES_TABS_ID } from './constants';
|
||||
|
||||
export const SettingsReleases = () => {
|
||||
const tabs = [
|
||||
{
|
||||
id: SETTINGS_RELEASES_TABS.CHANGELOG,
|
||||
title: t`Changelog`,
|
||||
Icon: IconRocket,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_RELEASES_TABS.LAB,
|
||||
title: t`Lab`,
|
||||
Icon: IconFlask,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Releases`}
|
||||
links={[
|
||||
{
|
||||
children: t`Other`,
|
||||
href: getSettingsPath(SettingsPath.Releases),
|
||||
},
|
||||
{ children: t`Releases` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={SETTINGS_RELEASES_TABS_ID}
|
||||
/>
|
||||
<SettingsReleasesTabContent />
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
+12
-31
@@ -1,5 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import remarkParse from 'remark-parse';
|
||||
@@ -7,11 +6,7 @@ import remarkRehype from 'remark-rehype';
|
||||
import { type PluggableList, unified } from 'unified';
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
type ReleaseNote = {
|
||||
slug: string;
|
||||
@@ -79,8 +74,7 @@ const StyledReleaseDate = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
export const Releases = () => {
|
||||
const { t } = useLingui();
|
||||
export const SettingsReleasesChangelogContent = () => {
|
||||
const [releases, setReleases] = useState<ReleaseNote[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -105,29 +99,16 @@ export const Releases = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Releases`}
|
||||
links={[
|
||||
{
|
||||
children: <Trans>Other</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: <Trans>Releases</Trans> },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<ScrollWrapper componentInstanceId="scroll-wrapper-releases">
|
||||
<StyledReleaseContainer>
|
||||
{releases.map((release) => (
|
||||
<React.Fragment key={release.slug}>
|
||||
<StyledReleaseHeader>{release.release}</StyledReleaseHeader>
|
||||
<StyledReleaseDate>{release.date}</StyledReleaseDate>
|
||||
<div dangerouslySetInnerHTML={{ __html: release.html }}></div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledReleaseContainer>
|
||||
</ScrollWrapper>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
<ScrollWrapper componentInstanceId="scroll-wrapper-releases">
|
||||
<StyledReleaseContainer>
|
||||
{releases.map((release) => (
|
||||
<React.Fragment key={release.slug}>
|
||||
<StyledReleaseHeader>{release.release}</StyledReleaseHeader>
|
||||
<StyledReleaseDate>{release.date}</StyledReleaseDate>
|
||||
<div dangerouslySetInnerHTML={{ __html: release.html }}></div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledReleaseContainer>
|
||||
</ScrollWrapper>
|
||||
);
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import {
|
||||
SETTINGS_RELEASES_TABS,
|
||||
SETTINGS_RELEASES_TABS_ID,
|
||||
} from '../constants';
|
||||
import { SettingsReleasesChangelogContent } from './SettingsReleasesChangelogContent';
|
||||
|
||||
export const SettingsReleasesTabContent = () => {
|
||||
const activeTabId = useRecoilComponentValue(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_RELEASES_TABS_ID,
|
||||
);
|
||||
|
||||
switch (activeTabId) {
|
||||
case SETTINGS_RELEASES_TABS.CHANGELOG:
|
||||
return <SettingsReleasesChangelogContent />;
|
||||
case SETTINGS_RELEASES_TABS.LAB:
|
||||
return <SettingsLabContent />;
|
||||
default:
|
||||
return <SettingsReleasesChangelogContent />;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { SettingsReleasesChangelogContent } from './SettingsReleasesChangelogContent';
|
||||
export { SettingsReleasesTabContent } from './SettingsReleasesTabContent';
|
||||
@@ -0,0 +1,4 @@
|
||||
export const SETTINGS_RELEASES_TABS = {
|
||||
CHANGELOG: 'changelog',
|
||||
LAB: 'lab',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const SETTINGS_RELEASES_TABS_ID = 'settings-releases-tabs';
|
||||
@@ -0,0 +1,2 @@
|
||||
export { SETTINGS_RELEASES_TABS } from './SettingsReleasesTabs';
|
||||
export { SETTINGS_RELEASES_TABS_ID } from './SettingsReleasesTabsId';
|
||||
@@ -0,0 +1 @@
|
||||
export { SettingsReleases } from './SettingsReleases';
|
||||
@@ -5,7 +5,7 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard';
|
||||
import { SettingsSecurityAuthProvidersOptionsList } from '@/settings/security/components/SettingsSecurityAuthProvidersOptionsList';
|
||||
import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard';
|
||||
|
||||
import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
@@ -63,13 +63,7 @@ export const SettingsSecurity = () => {
|
||||
/>
|
||||
<SettingsSSOIdentitiesProvidersListCard />
|
||||
</StyledSection>
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`Approved Domains`}
|
||||
description={t`Anyone with an email address at these domains is allowed to sign up for this workspace.`}
|
||||
/>
|
||||
<SettingsApprovedAccessDomainsListCard />
|
||||
</StyledSection>
|
||||
|
||||
<Section>
|
||||
<StyledContainer>
|
||||
<H2Title
|
||||
|
||||
+4
-4
@@ -65,7 +65,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Please check your email for a verification link.`,
|
||||
});
|
||||
navigate(SettingsPath.Security);
|
||||
navigate(SettingsPath.Domains);
|
||||
},
|
||||
onError: (error) => {
|
||||
enqueueErrorSnackBar({
|
||||
@@ -86,7 +86,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
||||
title="New Approved Access Domain"
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
onCancel={() => navigate(SettingsPath.Security)}
|
||||
onCancel={() => navigate(SettingsPath.Domains)}
|
||||
isSaveDisabled={form.formState.isSubmitting}
|
||||
/>
|
||||
}
|
||||
@@ -96,8 +96,8 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>Security</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Security),
|
||||
children: <Trans>Domains</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Domains),
|
||||
},
|
||||
{ children: <Trans>New Approved Access Domain</Trans> },
|
||||
]}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable';
|
||||
import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable';
|
||||
import { PlaygroundSetupForm } from '@/settings/playground/components/PlaygroundSetupForm';
|
||||
import { StyledSettingsApiPlaygroundCoverImage } from '@/settings/playground/components/SettingsPlaygroundCoverImage';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-top: ${({ theme }) => theme.spacing(5)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledMainContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(10)};
|
||||
min-height: 200px;
|
||||
`;
|
||||
|
||||
const StyledSection = styled(Section)`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div<{ isMobile?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: ${({ isMobile }) => (isMobile ? 'hidden' : 'visible')};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const SettingsApiWebhooks = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`APIs & Webhooks`}
|
||||
links={[
|
||||
{
|
||||
children: <Trans>Workspace</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: <Trans>APIs & Webhooks</Trans> },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<StyledMainContent>
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`Documentation`}
|
||||
description={t`Try our REST or GraphQL API playgrounds.`}
|
||||
/>
|
||||
<StyledContainer>
|
||||
<StyledSettingsApiPlaygroundCoverImage />
|
||||
<PlaygroundSetupForm />
|
||||
</StyledContainer>
|
||||
</StyledSection>
|
||||
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`API Keys`}
|
||||
description={t`Active API keys created by you or your team.`}
|
||||
/>
|
||||
<StyledContainer isMobile={isMobile}>
|
||||
<SettingsApiKeysTable />
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create API key`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
to={getSettingsPath(SettingsPath.NewApiKey)}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledContainer>
|
||||
</StyledSection>
|
||||
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title={t`Webhooks`}
|
||||
description={t`Establish Webhook endpoints for notifications on asynchronous events.`}
|
||||
/>
|
||||
<StyledContainer isMobile={isMobile}>
|
||||
<SettingsWebhooksTable />
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create webhook`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
to={getSettingsPath(SettingsPath.NewWebhook)}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledContainer>
|
||||
</StyledSection>
|
||||
</StyledMainContent>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@@ -215,14 +215,14 @@ export const SettingsDomain = () => {
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: <Trans>General</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
children: <Trans>Domains</Trans>,
|
||||
href: getSettingsPath(SettingsPath.Domains),
|
||||
},
|
||||
{ children: <Trans>Domain</Trans> },
|
||||
]}
|
||||
actionButton={
|
||||
<SaveAndCancelButtons
|
||||
onCancel={() => navigate(SettingsPath.Workspace)}
|
||||
onCancel={() => navigate(SettingsPath.Domains)}
|
||||
isSaveDisabled={form.formState.isSubmitting}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ enum SettingsPathPrefixes {
|
||||
Profile = `${AppBasePath.Settings}/${SettingsPath.ProfilePage}`,
|
||||
Objects = `${AppBasePath.Settings}/${SettingsPath.Objects}`,
|
||||
Members = `${AppBasePath.Settings}/${SettingsPath.WorkspaceMembersPage}`,
|
||||
ApiKeys = `${AppBasePath.Settings}/${SettingsPath.APIs}`,
|
||||
Webhooks = `${AppBasePath.Settings}/${SettingsPath.Webhooks}`,
|
||||
ApiWebhooks = `${AppBasePath.Settings}/${SettingsPath.ApiWebhooks}`,
|
||||
ServerlessFunctions = `${AppBasePath.Settings}/${SettingsPath.ServerlessFunctions}`,
|
||||
Integration = `${AppBasePath.Settings}/${SettingsPath.Integrations}`,
|
||||
General = `${AppBasePath.Settings}/${SettingsPath.Workspace}`,
|
||||
@@ -62,10 +61,8 @@ export const getPageTitleFromPath = (pathname: string): string => {
|
||||
return SettingsPageTitles.Members;
|
||||
case SettingsPathPrefixes.Objects:
|
||||
return SettingsPageTitles.Objects;
|
||||
case SettingsPathPrefixes.ApiKeys:
|
||||
case SettingsPathPrefixes.ApiWebhooks:
|
||||
return SettingsPageTitles.Apis;
|
||||
case SettingsPathPrefixes.Webhooks:
|
||||
return SettingsPageTitles.Webhooks;
|
||||
case SettingsPathPrefixes.ServerlessFunctions:
|
||||
return SettingsPageTitles.ServerlessFunctions;
|
||||
case SettingsPathPrefixes.Integration:
|
||||
|
||||
@@ -8,7 +8,7 @@ info: Discover how to use our APIs.
|
||||
## Overview
|
||||
The Twenty API allows developers to interact programmatically with the Twenty CRM platform. Using the API, you can integrate Twenty with other systems, automate data synchronization, and build custom solutions around your customer data. The API provides endpoints to **create, read, update, and delete** core CRM objects (such as people and companies) as well as access metadata configuration.
|
||||
|
||||
**API Playground:** You can now access the API Playground within the app's settings. To try out API calls in real-time, log in to your Twenty workspace and navigate to **Settings > API & Webhooks** (under **Developers**). This will open the in-app API Playground and settings for API keys. _(If you don’t see the API & Webhooks section in Settings, enable “Advanced mode” in your app settings to reveal developer options.)_
|
||||
**API Playground:** You can now access the API Playground within the app's settings. To try out API calls in real-time, log in to your Twenty workspace and navigate to **Settings → APIs & Webhooks**. This opens the in-app API Playground and the settings for API keys.
|
||||
**[Go to API Settings](https://app.twenty.com/settings)**
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -11,9 +11,9 @@ Webhooks in Twenty complement the API by enabling **real-time notifications** to
|
||||
With webhooks, Twenty will send an HTTP POST request to a URL you specify, containing details about the event. You can then handle that data in your application (e.g., to update your external database, trigger workflows, or send alerts).
|
||||
|
||||
## Setting Up a Webhook
|
||||
To create a webhook in Twenty, use the **API & Webhooks** settings in your Twenty app (Developer settings):
|
||||
To create a webhook in Twenty, use the **APIs & Webhooks** settings in your Twenty app:
|
||||
|
||||
1. **Navigate to Settings:** In your Twenty application, go to **Settings** (ensure Advanced mode is enabled to see developer options).
|
||||
1. **Navigate to Settings:** In your Twenty application, go to **Settings → APIs & Webhooks**.
|
||||
2. **Create a Webhook:** Under **Webhooks** click on **+ Create webhook**.
|
||||
3. **Enter URL:** Provide the endpoint URL on your server where you want Twenty to send webhook requests. This should be a publicly accessible URL that can handle POST requests.
|
||||
4. **Save:** Click **Save** to create the webhook. The new webhook will be active immediately.
|
||||
|
||||
@@ -37,7 +37,7 @@ From the `packages/twenty-zapier` folder, run:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
Run the application locally, go to [http://localhost:3000/settings/developers](http://localhost:3000/settings/developers/api-keys), and generate an API key.
|
||||
Run the application locally, go to [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), and generate an API key.
|
||||
|
||||
Replace the **YOUR_API_KEY** value in the `.env` file with the API key you just generated.
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ You control which contacts are imported in Twenty and what gets shared with the
|
||||
You can configure the visibility and the contact creation preferences under Settings → Email / Calendar.
|
||||
|
||||
### Using another provider?
|
||||
This is currently under beta: you can activate this feature under Settings → Lab.
|
||||
This is currently under beta: you can activate this feature under Settings → Releases → Lab tab.
|
||||
You can add email accounts from any provider that supports IMAP and send emails with SMTP. Synchronizing calendars with CalDAV is coming soon!
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ Open your Settings on the top left to
|
||||
- Invite members
|
||||
- Edit your profile
|
||||
- Customize your workspace: billing, user permissions
|
||||
- Discover the latest releases and the upcoming features (under the Lab)
|
||||
- Discover the latest releases and the upcoming features (under Releases → Lab tab)
|
||||
|
||||
If you do not see all those sections under Settings, reach out to your workspace administrator - some of them have a restricted access.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ This means you will have a dedicated endpoint for each of your custom objects an
|
||||
|
||||
|
||||
## API Documentation
|
||||
**Twenty API documentation is accessible under your workspace Settings -> API.**
|
||||
**Twenty API documentation is accessible under your workspace Settings → APIs & Webhooks.**
|
||||
As unique APIs are generated to perfectly reflect your data model, Twenty creates customized API documentation.
|
||||
This is why you need to input an API key to access it.
|
||||
You will also find a playground where you can test the APIs and the data returned.
|
||||
@@ -39,7 +39,7 @@ For example, you can use them to retrieve details of a specific `Person` or `Com
|
||||
### Generate an API key
|
||||
|
||||
1. Go to Settings in the sidebar on the left.
|
||||
2. In the bottom left, activate "Advanced mode." A new section for API & Webhooks will appear.
|
||||
2. Open **APIs & Webhooks**.
|
||||
3. To generate a new key, click on `+ Create key` at the top right.
|
||||
4. Give your API key a name, an expiration date, and a logo.
|
||||
5. Hit save to see your API key.
|
||||
@@ -80,7 +80,7 @@ For instance, a webhook can alert your system in real-time when someone creates
|
||||
|
||||
### Creating Webhooks
|
||||
|
||||
1. From Settings -> Developers, click `+` Create webhook.
|
||||
1. From Settings → APIs & Webhooks → Webhooks, click `+` Create webhook.
|
||||
2. Enter your URL.
|
||||
3. Click Save.
|
||||
|
||||
@@ -105,7 +105,7 @@ For instance, a webhook can alert your system in real-time when someone creates
|
||||
|
||||
### Deleting Webhooks
|
||||
|
||||
1. In Developers, find your webhook.
|
||||
1. In Settings → APIs & Webhooks → Webhooks, find your webhook.
|
||||
2. Click on your webhook and press **Delete** to remove it. A confirmation popup will appear to confirm.
|
||||
|
||||
<ArticleEditContent></ArticleEditContent>
|
||||
|
||||
@@ -96,7 +96,7 @@ This action runs custom JavaScript within your workflow. You can use this action
|
||||
|
||||
Any variables you create and return here can be reused in later steps of the workflow. You can also test your code directly in this step and view the output to confirm it works as expected.
|
||||
|
||||
Note: You can manage keys and access your API Playground [here](https://app.twenty.com/settings) or navigate to Settings > API & Webhooks (under Developers).
|
||||
Note: You can manage keys and access your API Playground [here](https://app.twenty.com/settings) or navigate to Settings → APIs & Webhooks.
|
||||
|
||||
### Form
|
||||
|
||||
|
||||
Reference in New Issue
Block a user