Releases page (#5346)
closes #4103 <img width="696" alt="Bildschirmfoto 2024-05-10 um 08 16 19" src="https://github.com/twentyhq/twenty/assets/48770548/e34cd348-2522-408c-886c-636595292e0f">
This commit is contained in:
@ -45,6 +45,7 @@ import { SettingsIntegrationEditDatabaseConnection } from '~/pages/settings/inte
|
|||||||
import { SettingsIntegrationNewDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection';
|
import { SettingsIntegrationNewDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection';
|
||||||
import { SettingsIntegrations } from '~/pages/settings/integrations/SettingsIntegrations';
|
import { SettingsIntegrations } from '~/pages/settings/integrations/SettingsIntegrations';
|
||||||
import { SettingsIntegrationShowDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection';
|
import { SettingsIntegrationShowDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection';
|
||||||
|
import { Releases } from '~/pages/settings/Releases';
|
||||||
import { SettingsAppearance } from '~/pages/settings/SettingsAppearance';
|
import { SettingsAppearance } from '~/pages/settings/SettingsAppearance';
|
||||||
import { SettingsBilling } from '~/pages/settings/SettingsBilling';
|
import { SettingsBilling } from '~/pages/settings/SettingsBilling';
|
||||||
import { SettingsProfile } from '~/pages/settings/SettingsProfile';
|
import { SettingsProfile } from '~/pages/settings/SettingsProfile';
|
||||||
@ -207,6 +208,7 @@ export const App = () => {
|
|||||||
path={SettingsPath.ObjectFieldEdit}
|
path={SettingsPath.ObjectFieldEdit}
|
||||||
element={<SettingsObjectFieldEdit />}
|
element={<SettingsObjectFieldEdit />}
|
||||||
/>
|
/>
|
||||||
|
<Route path={SettingsPath.Releases} element={<Releases />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
IconDoorEnter,
|
IconDoorEnter,
|
||||||
IconHierarchy2,
|
IconHierarchy2,
|
||||||
IconMail,
|
IconMail,
|
||||||
|
IconRocket,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
@ -105,6 +106,11 @@ export const SettingsNavigationDrawerItems = () => {
|
|||||||
|
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
<NavigationDrawerSectionTitle label="Other" />
|
<NavigationDrawerSectionTitle label="Other" />
|
||||||
|
<SettingsNavigationDrawerItem
|
||||||
|
label="Releases"
|
||||||
|
path={SettingsPath.Releases}
|
||||||
|
Icon={IconRocket}
|
||||||
|
/>
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
label="Logout"
|
label="Logout"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
||||||
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
const StyledSettingsPageContainer = styled.div<{ width?: number }>`
|
const StyledSettingsPageContainer = styled.div<{ width?: number }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -8,8 +10,15 @@ const StyledSettingsPageContainer = styled.div<{ width?: number }>`
|
|||||||
gap: ${({ theme }) => theme.spacing(8)};
|
gap: ${({ theme }) => theme.spacing(8)};
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: ${({ theme }) => theme.spacing(8)};
|
padding: ${({ theme }) => theme.spacing(8)};
|
||||||
width: ${({ width }) =>
|
width: ${({ width }) => {
|
||||||
width ? width + 'px' : OBJECT_SETTINGS_WIDTH + 'px'};
|
if (isDefined(width)) {
|
||||||
|
return width + 'px';
|
||||||
|
}
|
||||||
|
if (useIsMobile()) {
|
||||||
|
return 'unset';
|
||||||
|
}
|
||||||
|
return OBJECT_SETTINGS_WIDTH + 'px';
|
||||||
|
}};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledSettingsPageContainer as SettingsPageContainer };
|
export { StyledSettingsPageContainer as SettingsPageContainer };
|
||||||
|
|||||||
@ -27,4 +27,5 @@ export enum SettingsPath {
|
|||||||
IntegrationNewDatabaseConnection = 'integrations/:databaseKey/new',
|
IntegrationNewDatabaseConnection = 'integrations/:databaseKey/new',
|
||||||
DevelopersNewWebhook = 'webhooks/new',
|
DevelopersNewWebhook = 'webhooks/new',
|
||||||
DevelopersNewWebhookDetail = 'webhooks/:webhookId',
|
DevelopersNewWebhookDetail = 'webhooks/:webhookId',
|
||||||
|
Releases = 'releases',
|
||||||
}
|
}
|
||||||
|
|||||||
119
packages/twenty-front/src/pages/settings/Releases.tsx
Normal file
119
packages/twenty-front/src/pages/settings/Releases.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import rehypeStringify from 'rehype-stringify';
|
||||||
|
import remarkParse from 'remark-parse';
|
||||||
|
import remarkRehype from 'remark-rehype';
|
||||||
|
import { IconSettings } from 'twenty-ui';
|
||||||
|
import { unified } from 'unified';
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
|
import { H1Title } from '@/ui/display/typography/components/H1Title';
|
||||||
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
|
const StyledH1Title = styled(H1Title)`
|
||||||
|
margin-bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
margin: ${({ theme }) => theme.spacing(6)} 0px 0px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: ${({ theme }) => theme.background.tertiary};
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #474747;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledReleaseHeader = styled.h2`
|
||||||
|
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 Releases = () => {
|
||||||
|
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)
|
||||||
|
.use(remarkRehype)
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.use(() => (tree: any) => {
|
||||||
|
visit(tree, (node) => {
|
||||||
|
if (node.tagName === 'h1' || node.tagName === 'h2') {
|
||||||
|
node.tagName = 'h3';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.process(release.content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setReleases(json);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubMenuTopBarContainer Icon={IconSettings} title="Releases">
|
||||||
|
<SettingsPageContainer>
|
||||||
|
<StyledH1Title title="Releases" />
|
||||||
|
<ScrollWrapper>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -123,6 +123,7 @@ export {
|
|||||||
IconRelationOneToMany,
|
IconRelationOneToMany,
|
||||||
IconRelationOneToOne,
|
IconRelationOneToOne,
|
||||||
IconRepeat,
|
IconRepeat,
|
||||||
|
IconRocket,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconSend,
|
IconSend,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
|
|||||||
@ -1,20 +1,9 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
const allowedOrigins = [
|
export function middleware() {
|
||||||
'http://localhost:3000',
|
|
||||||
'https://app.twenty.com',
|
|
||||||
'https://twenty.com',
|
|
||||||
];
|
|
||||||
|
|
||||||
export function middleware(req: any) {
|
|
||||||
const res = NextResponse.next();
|
const res = NextResponse.next();
|
||||||
|
|
||||||
const origin = req.headers.get('origin');
|
res.headers.append('Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
if (allowedOrigins.includes(origin)) {
|
|
||||||
res.headers.append('Access-Control-Allow-Origin', origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.headers.append('Access-Control-Allow-Credentials', 'true');
|
res.headers.append('Access-Control-Allow-Credentials', 'true');
|
||||||
res.headers.append(
|
res.headers.append(
|
||||||
'Access-Control-Allow-Methods',
|
'Access-Control-Allow-Methods',
|
||||||
|
|||||||
Reference in New Issue
Block a user