mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
Fix self-hosting pricing page design. (#19930)
Some checks are pending
CD deploy main / deploy-main (push) Waiting to run
CI Create App E2E minimal / changed-files-check (push) Waiting to run
CI Create App E2E minimal / create-app-e2e-minimal (push) Blocked by required conditions
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Blocked by required conditions
CI Create App / changed-files-check (push) Waiting to run
CI Create App / create-app-test (lint) (push) Blocked by required conditions
CI Create App / create-app-test (test) (push) Blocked by required conditions
CI Create App / create-app-test (typecheck) (push) Blocked by required conditions
CI Create App / ci-create-app-status-check (push) Blocked by required conditions
CI Docs / changed-files-check (push) Waiting to run
CI Docs / docs-lint (push) Blocked by required conditions
CI Emails / changed-files-check (push) Waiting to run
CI Emails / emails-test (push) Blocked by required conditions
CI Emails / ci-emails-status-check (push) Blocked by required conditions
CI Example App Hello World / changed-files-check (push) Waiting to run
CI Example App Hello World / example-app-hello-world (push) Blocked by required conditions
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Blocked by required conditions
CI Example App Postcard / changed-files-check (push) Waiting to run
CI Example App Postcard / example-app-postcard (push) Blocked by required conditions
CI Example App Postcard / ci-example-app-postcard-status-check (push) Blocked by required conditions
Push docs to Crowdin / Push documentation to Crowdin (push) Waiting to run
Push translations to Crowdin / Extract and upload translations (push) Waiting to run
Some checks are pending
CD deploy main / deploy-main (push) Waiting to run
CI Create App E2E minimal / changed-files-check (push) Waiting to run
CI Create App E2E minimal / create-app-e2e-minimal (push) Blocked by required conditions
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Blocked by required conditions
CI Create App / changed-files-check (push) Waiting to run
CI Create App / create-app-test (lint) (push) Blocked by required conditions
CI Create App / create-app-test (test) (push) Blocked by required conditions
CI Create App / create-app-test (typecheck) (push) Blocked by required conditions
CI Create App / ci-create-app-status-check (push) Blocked by required conditions
CI Docs / changed-files-check (push) Waiting to run
CI Docs / docs-lint (push) Blocked by required conditions
CI Emails / changed-files-check (push) Waiting to run
CI Emails / emails-test (push) Blocked by required conditions
CI Emails / ci-emails-status-check (push) Blocked by required conditions
CI Example App Hello World / changed-files-check (push) Waiting to run
CI Example App Hello World / example-app-hello-world (push) Blocked by required conditions
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Blocked by required conditions
CI Example App Postcard / changed-files-check (push) Waiting to run
CI Example App Postcard / example-app-postcard (push) Blocked by required conditions
CI Example App Postcard / ci-example-app-postcard-status-check (push) Blocked by required conditions
Push docs to Crowdin / Push documentation to Crowdin (push) Waiting to run
Push translations to Crowdin / Extract and upload translations (push) Waiting to run
Resolve: https://discord.com/channels/1130383047699738754/1496108238909739079
This commit is contained in:
parent
2b399fc94e
commit
7947b1b843
2 changed files with 229 additions and 91 deletions
|
|
@ -1,6 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
BaseButton,
|
||||
buttonBaseStyles,
|
||||
} from '@/design-system/components/Button/BaseButton';
|
||||
import { Body, Heading } from '@/design-system/components';
|
||||
import { theme } from '@/theme';
|
||||
import { css } from '@linaria/core';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
|
@ -11,21 +17,10 @@ type ActivationResult = {
|
|||
subscriptionId: string;
|
||||
};
|
||||
|
||||
const PageWrap = styled.div`
|
||||
box-sizing: border-box;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: ${theme.spacing(12)};
|
||||
max-width: 700px;
|
||||
min-height: 60vh;
|
||||
padding-left: ${theme.spacing(4)};
|
||||
padding-right: ${theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: ${theme.font.size(8)};
|
||||
font-weight: 600;
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
const ContentStack = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${theme.spacing(6)};
|
||||
`;
|
||||
|
||||
const ErrorBox = styled.div`
|
||||
|
|
@ -33,27 +28,27 @@ const ErrorBox = styled.div`
|
|||
border: 1px solid ${theme.colors.accent.pink[70]};
|
||||
border-radius: ${theme.radius(2)};
|
||||
color: ${theme.colors.accent.pink[100]};
|
||||
font-family: ${theme.font.family.sans};
|
||||
font-size: ${theme.font.size(4)};
|
||||
line-height: 1.55;
|
||||
padding: ${theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const SuccessLead = styled.p`
|
||||
const successLeadClassName = css`
|
||||
color: ${theme.colors.accent.green[100]};
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const LicenseeRow = styled.div`
|
||||
margin-bottom: ${theme.spacing(6)};
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: ${theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const KeyLabel = styled.div`
|
||||
font-weight: 600;
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const KeyHint = styled.p`
|
||||
color: ${theme.colors.primary.text[60]};
|
||||
font-size: ${theme.font.size(3)};
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
const KeySection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const KeyBlock = styled.div`
|
||||
|
|
@ -69,34 +64,63 @@ const KeyBlock = styled.div`
|
|||
word-break: break-all;
|
||||
`;
|
||||
|
||||
const CopyButton = styled.button<{ $copied: boolean }>`
|
||||
background-color: ${({ $copied }) =>
|
||||
$copied ? theme.colors.accent.green[100] : theme.colors.primary.text[100]};
|
||||
border: none;
|
||||
border-radius: ${theme.radius(1)};
|
||||
color: ${theme.colors.primary.background[100]};
|
||||
cursor: pointer;
|
||||
font-size: ${theme.font.size(2)};
|
||||
padding: ${theme.spacing(2)} ${theme.spacing(3)};
|
||||
const CopyTrigger = styled.button<{ $copied: boolean }>`
|
||||
${buttonBaseStyles}
|
||||
position: absolute;
|
||||
right: ${theme.spacing(2)};
|
||||
top: ${theme.spacing(2)};
|
||||
|
||||
${({ $copied }) =>
|
||||
$copied
|
||||
? `
|
||||
& [data-slot='button-base-shape'] path,
|
||||
& [data-slot='button-base-shape'] rect {
|
||||
fill: ${theme.colors.accent.green[100]} !important;
|
||||
}
|
||||
|
||||
& [data-slot='button-hover-fill'] {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
& [data-slot='button-label'] {
|
||||
color: ${theme.colors.primary.background[100]} !important;
|
||||
}
|
||||
|
||||
&:is(:hover, :focus-visible) [data-slot='button-label'] {
|
||||
color: ${theme.colors.primary.background[100]} !important;
|
||||
}
|
||||
`
|
||||
: ''}
|
||||
`;
|
||||
|
||||
const NextStepsBox = styled.div`
|
||||
background-color: ${theme.colors.primary.border[5]};
|
||||
border: 1px solid ${theme.colors.accent.blue[70]};
|
||||
border: 1px solid ${theme.colors.primary.border[20]};
|
||||
border-left: 3px solid ${theme.colors.highlight[100]};
|
||||
border-radius: ${theme.radius(2)};
|
||||
margin-top: ${theme.spacing(8)};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${theme.spacing(2)};
|
||||
padding: ${theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const NextStepsList = styled.ol`
|
||||
line-height: 1.75;
|
||||
margin-top: ${theme.spacing(2)};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${theme.spacing(2)};
|
||||
list-style-position: outside;
|
||||
margin: 0;
|
||||
padding-left: ${theme.spacing(5)};
|
||||
`;
|
||||
|
||||
const nextStepItemClassName = css`
|
||||
&::marker {
|
||||
color: ${theme.colors.primary.text[60]};
|
||||
font-weight: ${theme.font.weight.medium};
|
||||
}
|
||||
`;
|
||||
|
||||
export function EnterpriseActivateClient() {
|
||||
const searchParams = useSearchParams();
|
||||
const sessionId = searchParams.get('session_id');
|
||||
|
|
@ -159,53 +183,105 @@ export function EnterpriseActivateClient() {
|
|||
};
|
||||
|
||||
return (
|
||||
<PageWrap>
|
||||
<Title>{'Enterprise activation'}</Title>
|
||||
|
||||
{loading && <p>{'Activating your enterprise license…'}</p>}
|
||||
<ContentStack>
|
||||
{loading && (
|
||||
<Body
|
||||
body={{ text: 'Activating your enterprise license…' }}
|
||||
size="sm"
|
||||
variant="body-paragraph"
|
||||
/>
|
||||
)}
|
||||
|
||||
{error !== null && <ErrorBox>{error}</ErrorBox>}
|
||||
|
||||
{result !== null && (
|
||||
<div>
|
||||
<SuccessLead>
|
||||
{'Your enterprise license has been activated successfully.'}
|
||||
</SuccessLead>
|
||||
<>
|
||||
<Body
|
||||
body={{
|
||||
text: 'Your enterprise license has been activated successfully.',
|
||||
}}
|
||||
className={successLeadClassName}
|
||||
size="md"
|
||||
weight="medium"
|
||||
/>
|
||||
|
||||
<LicenseeRow>
|
||||
<strong>{'Licensee:'}</strong> {result.licensee}
|
||||
<Body
|
||||
as="span"
|
||||
body={{ text: 'Licensee: ' }}
|
||||
size="sm"
|
||||
weight="medium"
|
||||
/>
|
||||
<Body as="span" body={{ text: result.licensee }} size="sm" />
|
||||
</LicenseeRow>
|
||||
|
||||
<KeyLabel>{'Your enterprise key'}</KeyLabel>
|
||||
<KeyHint>
|
||||
{
|
||||
'Copy this key and paste it into your Twenty self-hosted instance settings.'
|
||||
}
|
||||
</KeyHint>
|
||||
<KeySection>
|
||||
<Heading
|
||||
as="h2"
|
||||
segments={{ fontFamily: 'sans', text: 'Your enterprise key' }}
|
||||
size="xs"
|
||||
weight="medium"
|
||||
/>
|
||||
<Body
|
||||
body={{
|
||||
text: 'Copy this key and paste it into your Twenty self-hosted instance settings.',
|
||||
}}
|
||||
size="sm"
|
||||
variant="body-paragraph"
|
||||
/>
|
||||
|
||||
<KeyBlock>
|
||||
{result.enterpriseKey}
|
||||
<CopyButton
|
||||
$copied={copied}
|
||||
onClick={() => void handleCopy()}
|
||||
type="button"
|
||||
>
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</CopyButton>
|
||||
</KeyBlock>
|
||||
<KeyBlock>
|
||||
{result.enterpriseKey}
|
||||
<CopyTrigger
|
||||
$copied={copied}
|
||||
data-color="primary"
|
||||
data-size="small"
|
||||
data-variant="contained"
|
||||
onClick={() => void handleCopy()}
|
||||
type="button"
|
||||
>
|
||||
<BaseButton
|
||||
color="primary"
|
||||
label={copied ? 'Copied!' : 'Copy'}
|
||||
size="small"
|
||||
variant="contained"
|
||||
/>
|
||||
</CopyTrigger>
|
||||
</KeyBlock>
|
||||
</KeySection>
|
||||
|
||||
<NextStepsBox>
|
||||
<strong>{'Next steps'}</strong>
|
||||
<Heading
|
||||
as="h3"
|
||||
segments={{ fontFamily: 'sans', text: 'Next steps' }}
|
||||
size="xs"
|
||||
weight="medium"
|
||||
/>
|
||||
<NextStepsList>
|
||||
<li>{'Copy the enterprise key above.'}</li>
|
||||
<li>
|
||||
{'Open your Twenty self-hosted instance Settings → Enterprise.'}
|
||||
<li className={nextStepItemClassName}>
|
||||
<Body
|
||||
body={{ text: 'Copy the enterprise key above.' }}
|
||||
size="sm"
|
||||
/>
|
||||
</li>
|
||||
<li className={nextStepItemClassName}>
|
||||
<Body
|
||||
body={{
|
||||
text: 'Open your Twenty self-hosted instance Settings → Enterprise.',
|
||||
}}
|
||||
size="sm"
|
||||
/>
|
||||
</li>
|
||||
<li className={nextStepItemClassName}>
|
||||
<Body
|
||||
body={{ text: 'Paste the key and click Activate.' }}
|
||||
size="sm"
|
||||
/>
|
||||
</li>
|
||||
<li>{'Paste the key and click Activate.'}</li>
|
||||
</NextStepsList>
|
||||
</NextStepsBox>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</PageWrap>
|
||||
</ContentStack>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
import { MENU_DATA } from '@/app/_constants';
|
||||
import { EnterpriseActivateClient } from '@/app/enterprise/activate/EnterpriseActivateClient';
|
||||
import { Body, Container, Eyebrow } from '@/design-system/components';
|
||||
import type { HeadingType } from '@/design-system/components/Heading/types/Heading';
|
||||
import { Pages } from '@/enums/pages';
|
||||
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
|
||||
import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels';
|
||||
import { Hero } from '@/sections/Hero/components';
|
||||
import { Menu } from '@/sections/Menu/components';
|
||||
import { theme } from '@/theme';
|
||||
import { css } from '@linaria/core';
|
||||
import type { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { EnterpriseActivateClient } from './EnterpriseActivateClient';
|
||||
|
||||
const activateFallbackClassName = css`
|
||||
box-sizing: border-box;
|
||||
color: ${theme.colors.primary.text[60]};
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: ${theme.spacing(12)};
|
||||
max-width: 700px;
|
||||
padding-left: ${theme.spacing(4)};
|
||||
padding-right: ${theme.spacing(4)};
|
||||
`;
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Enterprise activation | Twenty',
|
||||
|
|
@ -22,16 +18,82 @@ export const metadata: Metadata = {
|
|||
'Complete activation for your Twenty self-hosted enterprise license.',
|
||||
};
|
||||
|
||||
const ENTERPRISE_ACTIVATE_HEADING: HeadingType[] = [
|
||||
{ text: 'Enterprise ', fontFamily: 'serif' },
|
||||
{ text: 'activation', fontFamily: 'sans' },
|
||||
];
|
||||
|
||||
const ENTERPRISE_ACTIVATE_BODY = {
|
||||
text: 'Your checkout is complete. Follow the steps below to copy your license key into your Twenty instance.',
|
||||
};
|
||||
|
||||
const ActivatePageContent = styled.section`
|
||||
background-color: ${theme.colors.primary.background[100]};
|
||||
flex: 1;
|
||||
padding-bottom: ${theme.spacing(20)};
|
||||
padding-top: ${theme.spacing(8)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ActivateContentInner = styled.div`
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
function EnterpriseActivateFallback() {
|
||||
return (
|
||||
<div className={activateFallbackClassName}>{'Loading activation…'}</div>
|
||||
<Body
|
||||
body={{ text: 'Loading activation…' }}
|
||||
size="sm"
|
||||
variant="body-paragraph"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EnterpriseActivatePage() {
|
||||
export default async function EnterpriseActivatePage() {
|
||||
const stats = await fetchCommunityStats();
|
||||
const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<EnterpriseActivateFallback />}>
|
||||
<EnterpriseActivateClient />
|
||||
</Suspense>
|
||||
<>
|
||||
<Menu.Root
|
||||
backgroundColor="#F3F3F3"
|
||||
scheme="primary"
|
||||
navItems={MENU_DATA.navItems}
|
||||
socialLinks={menuSocialLinks}
|
||||
>
|
||||
<Menu.Logo scheme="primary" />
|
||||
<Menu.Nav scheme="primary" navItems={MENU_DATA.navItems} />
|
||||
<Menu.Social scheme="primary" socialLinks={menuSocialLinks} />
|
||||
<Menu.Cta scheme="primary" />
|
||||
</Menu.Root>
|
||||
|
||||
<Hero.Root
|
||||
backgroundColor={theme.colors.secondary.background[5]}
|
||||
colorScheme="primary"
|
||||
>
|
||||
<Eyebrow
|
||||
colorScheme="primary"
|
||||
heading={{ text: 'Self-hosting', fontFamily: 'sans' }}
|
||||
/>
|
||||
<Hero.Heading
|
||||
page={Pages.Pricing}
|
||||
segments={ENTERPRISE_ACTIVATE_HEADING}
|
||||
/>
|
||||
<Hero.Body body={ENTERPRISE_ACTIVATE_BODY} page={Pages.Pricing} />
|
||||
</Hero.Root>
|
||||
|
||||
<ActivatePageContent>
|
||||
<Container>
|
||||
<ActivateContentInner>
|
||||
<Suspense fallback={<EnterpriseActivateFallback />}>
|
||||
<EnterpriseActivateClient />
|
||||
</Suspense>
|
||||
</ActivateContentInner>
|
||||
</Container>
|
||||
</ActivatePageContent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue