Fixes and updates to the website. (#19350)

This PR implements a whole lot of fixes and updates to the website. It
adds a release-notes page, terms and conditions page, privacy policy
page, while fixing HomeStepper, ProductStepper, and WhyTwentyStepper. 3D
models still need to be styled and their sizes need to be fixed.
This commit is contained in:
Abdullah.
2026-04-06 12:42:30 +05:00
committed by GitHub
parent e062343802
commit 3a1e112b86
150 changed files with 3690 additions and 1275 deletions
@@ -0,0 +1,32 @@
import { theme } from '@/theme';
import { styled } from '@linaria/react';
const DividerOuter = styled.div`
margin: ${theme.spacing(10)} 0;
width: 100%;
@media (min-width: ${theme.breakpoints.md}px) {
display: flex;
justify-content: flex-end;
}
`;
const DividerLine = styled.div`
background-color: ${theme.colors.primary.border[20]};
height: 1px;
width: 100%;
@media (min-width: ${theme.breakpoints.md}px) {
margin-left: calc(132px + ${theme.spacing(10)});
width: auto;
flex: 1 1 0;
}
`;
export function Divider() {
return (
<DividerOuter aria-hidden>
<DividerLine />
</DividerOuter>
);
}
@@ -0,0 +1,75 @@
import { formatReleaseDisplayDate } from '@/lib/releases/format-release-display-date';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import { ReleaseMarkdown } from '../ReleaseMarkdown/ReleaseMarkdown';
const ArticleRow = styled.article`
column-gap: ${theme.spacing(6)};
display: grid;
grid-template-columns: minmax(0, 1fr);
min-width: 0;
row-gap: ${theme.spacing(4)};
@media (min-width: ${theme.breakpoints.md}px) {
column-gap: ${theme.spacing(10)};
grid-template-columns: minmax(96px, 132px) minmax(0, 1fr);
row-gap: 0;
}
`;
const MetaColumn = styled.div`
align-items: flex-start;
display: flex;
flex-direction: row;
justify-content: space-between;
min-width: 0;
@media (min-width: ${theme.breakpoints.md}px) {
flex-direction: column;
padding-top: ${theme.spacing(2)};
}
`;
const Version = styled.span`
color: ${theme.colors.primary.text[100]};
font-size: ${theme.font.size(5)};
font-weight: ${theme.font.weight.medium};
line-height: 1.3;
`;
const DateText = styled.span`
color: ${theme.colors.primary.text[60]};
font-size: ${theme.font.size(3)};
white-space: nowrap;
@media (min-width: ${theme.breakpoints.md}px) {
margin-top: ${theme.spacing(2)};
white-space: normal;
}
`;
const ContentColumn = styled.div`
min-width: 0;
`;
type ReleaseEntryProps = {
content: string;
date: string;
release: string;
};
export function ReleaseEntry({ content, date, release }: ReleaseEntryProps) {
const displayDate = formatReleaseDisplayDate(date);
return (
<ArticleRow>
<MetaColumn>
<Version>{release}</Version>
<DateText>{displayDate || '—'}</DateText>
</MetaColumn>
<ContentColumn>
<ReleaseMarkdown markdown={content} />
</ContentColumn>
</ArticleRow>
);
}
@@ -0,0 +1,155 @@
'use client';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
const MARKETING_ORIGIN =
process.env.NEXT_PUBLIC_MARKETING_ASSET_ORIGIN ?? 'https://twenty.com';
function resolveAssetUrl(url: string | undefined): string {
if (!url) {
return '';
}
if (url.startsWith('/')) {
return `${MARKETING_ORIGIN}${url}`;
}
return url;
}
const Prose = styled.div`
max-width: 100%;
min-width: 0;
h1 {
color: ${theme.colors.primary.text[100]};
font-size: ${theme.font.size(8)};
font-weight: ${theme.font.weight.medium};
line-height: 1.25;
margin: 0;
margin-top: ${theme.spacing(8)};
&:first-child {
margin-top: 0;
}
}
h2 {
color: ${theme.colors.primary.text[100]};
font-size: ${theme.font.size(6)};
font-weight: ${theme.font.weight.medium};
line-height: 1.3;
margin: 0;
margin-top: ${theme.spacing(8)};
&:first-child {
margin-top: 0;
}
}
h3 {
color: ${theme.colors.primary.text[100]};
font-size: ${theme.font.size(5)};
font-weight: ${theme.font.weight.medium};
line-height: 1.35;
margin: 0;
margin-top: ${theme.spacing(6)};
}
p {
color: ${theme.colors.primary.text[80]};
font-size: ${theme.font.size(4)};
line-height: 1.65;
margin: 0;
margin-top: ${theme.spacing(4)};
}
ul,
ol {
color: ${theme.colors.primary.text[80]};
font-size: ${theme.font.size(4)};
line-height: 1.65;
margin: ${theme.spacing(4)} 0 0;
padding-left: ${theme.spacing(6)};
}
li {
margin-top: ${theme.spacing(2)};
}
a {
color: ${theme.colors.highlight[100]};
text-decoration: underline;
text-underline-offset: 2px;
}
code {
background-color: ${theme.colors.primary.border[10]};
border-radius: ${theme.radius(1)};
font-family: ${theme.font.family.mono};
font-size: 0.9em;
padding: 0.1em 0.35em;
}
pre {
background-color: ${theme.colors.primary.border[10]};
border-radius: ${theme.radius(2)};
margin-top: ${theme.spacing(4)};
overflow: auto;
padding: ${theme.spacing(4)};
}
pre code {
background: none;
padding: 0;
}
img {
border-radius: ${theme.radius(2)};
display: block;
height: auto;
margin-top: ${theme.spacing(6)};
max-width: 100%;
}
hr {
border: none;
border-top: 1px solid ${theme.colors.primary.border[20]};
margin: ${theme.spacing(8)} 0 0;
}
blockquote {
border-left: 2px solid ${theme.colors.primary.border[40]};
color: ${theme.colors.primary.text[60]};
margin: ${theme.spacing(4)} 0 0;
padding-left: ${theme.spacing(4)};
}
`;
type ReleaseMarkdownProps = {
markdown: string;
};
export function ReleaseMarkdown({ markdown }: ReleaseMarkdownProps) {
return (
<Prose>
<ReactMarkdown
components={{
a: ({ children, href, ...props }) => (
<a href={resolveAssetUrl(href)} rel="noopener noreferrer" {...props}>
{children}
</a>
),
img: ({ alt, src, ...props }) => (
<img alt={alt ?? ''} src={resolveAssetUrl(src)} {...props} />
),
}}
remarkPlugins={[remarkGfm]}
urlTransform={(value) => value}
>
{markdown}
</ReactMarkdown>
</Prose>
);
}
@@ -0,0 +1,52 @@
import { Container } from '@/design-system/components';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import type { ReactNode } from 'react';
const StyledSection = styled.section`
background-color: ${theme.colors.primary.background[100]};
min-width: 0;
width: 100%;
`;
const StyledContainer = styled(Container)`
padding-bottom: ${theme.spacing(20)};
padding-left: ${theme.spacing(4)};
padding-right: ${theme.spacing(4)};
padding-top: ${theme.spacing(4)};
@media (min-width: ${theme.breakpoints.md}px) {
padding-bottom: ${theme.spacing(28)};
padding-left: ${theme.spacing(10)};
padding-right: ${theme.spacing(10)};
}
`;
const EmptyMessage = styled.p`
color: ${theme.colors.primary.text[60]};
font-size: ${theme.font.size(4)};
line-height: 1.65;
margin: 0;
max-width: 40rem;
strong {
color: ${theme.colors.primary.text[80]};
font-family: ${theme.font.family.mono};
font-size: ${theme.font.size(3)};
font-weight: ${theme.font.weight.medium};
}
`;
type RootProps = {
children: ReactNode;
};
export function Root({ children }: RootProps) {
return (
<StyledSection aria-label="Release notes">
<StyledContainer>{children}</StyledContainer>
</StyledSection>
);
}
export { EmptyMessage };
@@ -0,0 +1,10 @@
import { Divider } from './Divider/Divider';
import { EmptyMessage, Root } from './Root/Root';
import { ReleaseEntry } from './ReleaseEntry/ReleaseEntry';
export const ReleaseNotes = {
Divider,
EmptyMessage,
ReleaseEntry,
Root,
};