5078 ability to invite team members (#5750)

## Added features
- update team member setting page
- add a section to send invitation by email
- add a new invitation email
- update email font to 'Trebuchet MS' as Google Inter font is not
working, we need to use a web safe font
https://templates.mailchimp.com/design/typography/

## Demo

https://github.com/twentyhq/twenty/assets/29927851/c731d883-1599-4281-87e3-0671f36994ae

## Invitation Email

![image](https://github.com/twentyhq/twenty/assets/29927851/d569fc64-fa0c-4769-a3dd-1193a12b495c)
This commit is contained in:
martmull
2024-06-05 16:35:14 +02:00
committed by GitHub
parent 3c4b497846
commit e9d3ed99ca
28 changed files with 608 additions and 39 deletions

View File

@ -27,17 +27,25 @@ export const emailTheme = {
colors: {
highlighted: grayScale.gray60,
primary: grayScale.gray50,
tertiary: grayScale.gray40,
tertiary: grayScale.gray35,
inverted: grayScale.gray0,
},
family: 'Trebuchet MS', // Google Inter not working, we need to use a web safe font, see https://templates.mailchimp.com/design/typography/
weight: {
regular: 400,
bold: 600,
},
size: {
sm: '12px',
md: '13px',
lg: '16px',
xl: '24px',
},
lineHeight: '20px',
},
border: {
radius: { sm: '4px', md: '8px' },
color: { highlighted: grayScale.gray20 },
},
background: {
colors: { highlight: grayScale.gray15 },

View File

@ -7,12 +7,8 @@ export const BaseHead = () => {
<Head>
<title>Twenty email</title>
<Font
fontFamily="Inter"
fontFamily={emailTheme.font.family}
fallbackFontFamily="sans-serif"
webFont={{
url: 'https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap',
format: 'woff2',
}}
fontStyle="normal"
fontWeight={emailTheme.font.weight.regular}
/>

View File

@ -6,7 +6,7 @@ import { emailTheme } from 'src/common-style';
const callToActionStyle = {
display: 'flex',
padding: '8px 32px',
borderRadius: '8px',
borderRadius: emailTheme.border.radius.md,
border: `1px solid ${emailTheme.background.transparent.light}`,
background: emailTheme.background.radialGradient,
boxShadow: `0px 2px 4px 0px ${emailTheme.background.transparent.light}, 0px 0px 4px 0px ${emailTheme.background.transparent.medium}`,

View File

@ -0,0 +1,33 @@
import React, { PropsWithChildren } from 'react';
import { Container } from '@react-email/components';
import { emailTheme } from 'src/common-style';
type HighlightedContainerProps = PropsWithChildren;
const highlightedContainerStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: emailTheme.background.colors.highlight,
border: `1px solid ${emailTheme.border.color.highlighted}`,
borderRadius: emailTheme.border.radius.md,
padding: '24px 48px',
gap: '24px',
};
const divStyle = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
} as React.CSSProperties;
export const HighlightedContainer = ({
children,
}: HighlightedContainerProps) => {
return (
<Container style={highlightedContainerStyle}>
<div style={divStyle}>{children}</div>
</Container>
);
};

View File

@ -1,34 +1,30 @@
import { ReactNode } from 'react';
import { Column } from '@react-email/components';
import { Row } from '@react-email/row';
import React, { ReactNode } from 'react';
import { Text } from '@react-email/text';
import { emailTheme } from 'src/common-style';
const rowStyle = {
display: 'flex',
};
const highlightedStyle = {
borderRadius: '4px',
borderRadius: emailTheme.border.radius.sm,
background: emailTheme.background.colors.highlight,
padding: '4px 8px',
margin: 0,
fontSize: emailTheme.font.size.lg,
fontWeight: emailTheme.font.weight.bold,
color: emailTheme.font.colors.highlighted,
};
const divStyle = {
display: 'flex',
};
type HighlightedTextProps = {
value: ReactNode;
centered?: boolean;
};
export const HighlightedText = ({ value }: HighlightedTextProps) => {
return (
<Row style={rowStyle}>
<Column>
<Text style={highlightedStyle}>{value}</Text>
</Column>
</Row>
<div style={divStyle}>
<Text style={highlightedStyle}>{value}</Text>
</div>
);
};

View File

@ -4,9 +4,12 @@ import { Text } from '@react-email/text';
import { emailTheme } from 'src/common-style';
const mainTextStyle = {
fontFamily: emailTheme.font.family,
fontSize: emailTheme.font.size.md,
fontWeight: emailTheme.font.weight.regular,
color: emailTheme.font.colors.primary,
margin: '0 0 12px 0',
lineHeight: emailTheme.font.lineHeight,
};
export const MainText = ({ children }: MainTextProps) => {

View File

@ -0,0 +1,16 @@
import { PropsWithChildren as ShadowTextProps } from 'react';
import { Text } from '@react-email/text';
import { emailTheme } from 'src/common-style';
const shadowTextStyle = {
fontSize: emailTheme.font.size.sm,
fontWeight: emailTheme.font.weight.regular,
color: emailTheme.font.colors.tertiary,
margin: '0 0 12px 0',
lineHeight: emailTheme.font.lineHeight,
};
export const ShadowText = ({ children }: ShadowTextProps) => {
return <Text style={shadowTextStyle}>{children}</Text>;
};

View File

@ -0,0 +1,23 @@
import { ReactNode } from 'react';
import { Heading } from '@react-email/components';
import { emailTheme } from 'src/common-style';
type SubTitleProps = {
value: ReactNode;
};
const subTitleStyle = {
fontFamily: emailTheme.font.family,
fontSize: emailTheme.font.size.lg,
fontWeight: emailTheme.font.weight.bold,
color: emailTheme.font.colors.highlighted,
};
export const SubTitle = ({ value }: SubTitleProps) => {
return (
<Heading style={subTitleStyle} as="h3">
{value}
</Heading>
);
};

View File

@ -1,10 +1,23 @@
import { ReactNode } from 'react';
import { Heading } from '@react-email/components';
import { emailTheme } from 'src/common-style';
type TitleProps = {
value: ReactNode;
};
export const Title = ({ value }: TitleProps) => {
return <Heading as="h1">{value}</Heading>;
const titleStyle = {
fontFamily: emailTheme.font.family,
fontSize: emailTheme.font.size.xl,
fontWeight: emailTheme.font.weight.bold,
color: emailTheme.font.colors.highlighted,
};
export const Title = ({ value }: TitleProps) => {
return (
<Heading style={titleStyle} as="h1">
{value}
</Heading>
);
};

View File

@ -0,0 +1,47 @@
import { Column, Row } from '@react-email/components';
import { Link } from 'src/components/Link';
import { MainText } from 'src/components/MainText';
import { ShadowText } from 'src/components/ShadowText';
import { SubTitle } from 'src/components/SubTitle';
export const WhatIsTwenty = () => {
return (
<>
<SubTitle value="What is Twenty?" />
<MainText>
A software to help businesses manage their customer data and
relationships efficiently.
</MainText>
<Row>
<Column>
<ShadowText>
<Link href="https://twenty.com/" value="Website" />
</ShadowText>
</Column>
<Column>
<ShadowText>
<Link href="https://github.com/twentyhq/twenty" value="Github" />
</ShadowText>
</Column>
<Column>
<ShadowText>
<Link href="https://twenty.com/user-guide" value="User guide" />
</ShadowText>
</Column>
<Column>
<ShadowText>
<Link href="https://docs.twenty.com/" value="Developers" />
</ShadowText>
</Column>
</Row>
<ShadowText>
Twenty.com Public Benefit Corporation
<br />
2261 Market Street #5275
<br />
San Francisco, CA 94114
</ShadowText>
</>
);
};

View File

@ -0,0 +1,44 @@
import { Img } from '@react-email/components';
import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { HighlightedContainer } from 'src/components/HighlightedContainer';
import { HighlightedText } from 'src/components/HighlightedText';
import { Link } from 'src/components/Link';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
import { capitalize } from 'src/utils/capitalize';
type SendInviteLinkEmailProps = {
link: string;
workspace: { name: string | undefined; logo: string | undefined };
sender: {
email: string;
firstName: string;
};
};
export const SendInviteLinkEmail = ({
link,
workspace,
sender,
}: SendInviteLinkEmailProps) => {
return (
<BaseEmail width={333}>
<Title value="Join your team on Twenty" />
<MainText>
{capitalize(sender.firstName)} (
<Link href={sender.email} value={sender.email} />) has invited you to
join a workspace called <b>{workspace.name}</b>
<br />
</MainText>
<HighlightedContainer>
{workspace.logo && <Img src={workspace.logo} width={40} height={40} />}
{workspace.name && <HighlightedText value={workspace.name} />}
<CallToAction href={link} value="Accept invite" />
</HighlightedContainer>
<WhatIsTwenty />
</BaseEmail>
);
};

View File

@ -2,3 +2,4 @@ export * from './emails/clean-inactive-workspaces.email';
export * from './emails/delete-inactive-workspaces.email';
export * from './emails/password-reset-link.email';
export * from './emails/password-update-notify.email';
export * from './emails/send-invite-link.email';

View File

@ -0,0 +1,7 @@
import { isNonEmptyString } from '@sniptt/guards';
export const capitalize = (stringToCapitalize: string) => {
if (!isNonEmptyString(stringToCapitalize)) return '';
return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1);
};