feat: rename Releases settings page to Updates (#16634)

- Rename page title from 'Releases' to 'Updates'
- Rename navigation item label from 'Releases' to 'Updates'
- Remove tabbed interface (Changelog/Lab tabs)
- Add 'Releases' section with external link to changelog
- Add 'Early access' section with lab features
- Add IconTransform to twenty-ui exports
- Delete unused tab-related components and constants

Screenshot:

<img width="2158" height="1698" alt="CleanShot 2025-12-17 at 17 53
46@2x"
src="https://github.com/user-attachments/assets/87ead041-24a7-4afb-9dfc-71e5c20324d6"
/>

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas des Francs
2025-12-19 15:39:35 +01:00
committed by GitHub
parent 2eb8006a30
commit 9a094d8a50
14 changed files with 73 additions and 205 deletions
@@ -340,9 +340,9 @@ const SettingsAdminConfigVariableDetails = lazy(() =>
})),
);
const SettingsReleases = lazy(() =>
import('~/pages/settings/releases/SettingsReleases').then((module) => ({
default: module.SettingsReleases,
const SettingsUpdates = lazy(() =>
import('~/pages/settings/updates/SettingsUpdates').then((module) => ({
default: module.SettingsUpdates,
})),
);
@@ -625,7 +625,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
/>
}
>
<Route path={SettingsPath.Releases} element={<SettingsReleases />} />
<Route path={SettingsPath.Updates} element={<SettingsUpdates />} />
</Route>
</Routes>
</Suspense>
@@ -202,8 +202,8 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
isHidden: !isAdminEnabled,
},
{
label: t`Releases`,
path: SettingsPath.Releases,
label: t`Updates`,
path: SettingsPath.Updates,
Icon: IconRocket,
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
},
@@ -1,47 +0,0 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconFlask, IconRocket } from 'twenty-ui/display';
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>
);
};
@@ -1,117 +0,0 @@
import styled from '@emotion/styled';
import React, { useEffect, useState } from 'react';
import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import { type PluggableList, unified } from 'unified';
import { visit } from 'unist-util-visit';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { formatToHumanReadableDate } from '~/utils/date-utils';
type ReleaseNote = {
slug: string;
date: string;
release: string;
content: string;
html: string;
};
const StyledReleaseContainer = styled.div`
img {
margin: ${({ theme }) => theme.spacing(6)} 0px 0px;
max-width: 100%;
}
p img {
margin: 0px;
}
h3 {
color: ${({ theme }) => theme.font.color.primary};
margin: ${({ theme }) => theme.spacing(6)} 0px 0px;
}
code {
background: ${({ theme }) => theme.background.tertiary};
padding: 4px;
border-radius: 4px;
}
p {
color: ${({ theme }) => theme.font.color.secondary};
font-family: Inter, sans-serif;
font-size: ${({ theme }) => theme.font.size.md};
line-height: 19.5px;
font-weight: ${({ theme }) => theme.font.weight.regular};
margin: ${({ theme }) => theme.spacing(6)} 0px 0px;
text-align: justify;
}
li {
color: ${({ theme }) => theme.font.color.secondary};
}
li strong {
color: ${({ theme }) => theme.font.color.primary};
}
`;
const StyledReleaseHeader = styled.h2`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
line-height: 18px;
font-size: ${({ theme }) => theme.font.size.md};
margin: 0;
margin-top: ${({ theme }) => theme.spacing(10)};
&:first-of-type {
margin-top: 0;
}
`;
const StyledReleaseDate = styled.span`
font-weight: ${({ theme }) => theme.font.weight.regular};
font-size: 12px;
line-height: 18px;
color: ${({ theme }) => theme.font.color.tertiary};
`;
export const SettingsReleasesChangelogContent = () => {
const [releases, setReleases] = useState<ReleaseNote[]>([]);
useEffect(() => {
fetch('https://twenty.com/api/releases').then(async (res) => {
const json = await res.json();
for (const release of json) {
release.html = String(
await unified()
.use([remarkParse, remarkRehype, rehypeStringify] as PluggableList)
.use(() => (tree: any) => {
visit(tree, (node) => {
if (node.tagName === 'h1' || node.tagName === 'h2') {
node.tagName = 'h3';
}
});
})
.process(release.content),
);
}
setReleases(json);
});
}, []);
return (
<ScrollWrapper componentInstanceId="scroll-wrapper-releases">
<StyledReleaseContainer>
{releases.map((release) => (
<React.Fragment key={release.slug}>
<StyledReleaseHeader>{release.release}</StyledReleaseHeader>
<StyledReleaseDate>
{formatToHumanReadableDate(release.date)}
</StyledReleaseDate>
<div dangerouslySetInnerHTML={{ __html: release.html }}></div>
</React.Fragment>
))}
</StyledReleaseContainer>
</ScrollWrapper>
);
};
@@ -1,24 +0,0 @@
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 />;
}
};
@@ -1,2 +0,0 @@
export { SettingsReleasesChangelogContent } from './SettingsReleasesChangelogContent';
export { SettingsReleasesTabContent } from './SettingsReleasesTabContent';
@@ -1,4 +0,0 @@
export const SETTINGS_RELEASES_TABS = {
CHANGELOG: 'changelog',
LAB: 'lab',
};
@@ -1 +0,0 @@
export const SETTINGS_RELEASES_TABS_ID = 'settings-releases-tabs';
@@ -1,2 +0,0 @@
export { SETTINGS_RELEASES_TABS } from './SettingsReleasesTabs';
export { SETTINGS_RELEASES_TABS_ID } from './SettingsReleasesTabsId';
@@ -1 +0,0 @@
export { SettingsReleases } from './SettingsReleases';
@@ -0,0 +1,64 @@
import { SettingsCard } from '@/settings/components/SettingsCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { H2Title, IconTransform } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const StyledCardLink = styled.a`
text-decoration: none;
`;
export const SettingsUpdates = () => {
const theme = useTheme();
return (
<SubMenuTopBarContainer
title={t`Updates`}
links={[
{
children: t`Other`,
href: getSettingsPath(SettingsPath.Updates),
},
{ children: t`Updates` },
]}
>
<SettingsPageContainer>
<Section>
<H2Title
title={t`Releases`}
description={t`Check out our latest releases`}
/>
<StyledCardLink
href="https://twenty.com/releases"
target="_blank"
rel="noopener noreferrer"
>
<SettingsCard
Icon={
<IconTransform
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
}
title={t`Read changelog`}
/>
</StyledCardLink>
</Section>
<Section>
<H2Title
title={t`Early access`}
description={t`Try our upcoming features. Note they are still in beta. Please bear with us and report any issues you find.`}
/>
<SettingsLabContent />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};
@@ -26,7 +26,7 @@ export enum SettingsPath {
NewApprovedAccessDomain = 'domains/approved-access-domain/new',
NewEmailingDomain = 'domains/emailing-domain/new',
EmailingDomainDetail = 'domains/emailing-domain/:domainId',
Releases = 'releases',
Updates = 'updates',
AI = 'ai',
AINewAgent = 'ai/new-agent',
AIAgentDetail = 'ai/agents/:agentId',
@@ -357,6 +357,7 @@ export {
IconTimeDuration60,
IconTimelineEvent,
IconTool,
IconTransform,
IconTrash,
IconTrashOff,
IconTrashX,
+1
View File
@@ -422,6 +422,7 @@ export {
IconTimeDuration60,
IconTimelineEvent,
IconTool,
IconTransform,
IconTrash,
IconTrashOff,
IconTrashX,