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:
committed by
GitHub
parent
2eb8006a30
commit
9a094d8a50
@@ -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>
|
||||
);
|
||||
};
|
||||
-117
@@ -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>
|
||||
);
|
||||
};
|
||||
-24
@@ -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,
|
||||
|
||||
@@ -422,6 +422,7 @@ export {
|
||||
IconTimeDuration60,
|
||||
IconTimelineEvent,
|
||||
IconTool,
|
||||
IconTransform,
|
||||
IconTrash,
|
||||
IconTrashOff,
|
||||
IconTrashX,
|
||||
|
||||
Reference in New Issue
Block a user