-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Added dynamic OG Image to share and download in contributors page <img width="1176" alt="Screenshot 2024-05-02 at 16 24 00" src="https://github.com/twentyhq/twenty/assets/102751374/0579454b-ccc7-46ba-9875-52458f06ee82"> - Added dynamic metadata - Added design to contributor page - Added a NEXT_PUBLIC_HOST_URL in the .env file Co-authored-by: Ady Beraud <[email protected]>
- Loading branch information
1 parent
a5a9e0e
commit 2067069
Showing
6 changed files
with
507 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
GITHUB_TOKEN=your_github_token | ||
DATABASE_PG_URL=postgres://website:website@localhost:5432/website # only if using postgres | ||
NEXT_PUBLIC_HOST_URL=http://localhost:3000 | ||
|
94 changes: 94 additions & 0 deletions
94
packages/twenty-website/src/app/_components/contributors/ProfileSharing.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
'use client'; | ||
|
||
import styled from '@emotion/styled'; | ||
import { IconDownload } from '@tabler/icons-react'; | ||
|
||
import { CardContainer } from '@/app/_components/contributors/CardContainer'; | ||
import { GithubIcon, XIcon } from '@/app/_components/ui/icons/SvgIcons'; | ||
import { Theme } from '@/app/_components/ui/theme/theme'; | ||
|
||
const Container = styled(CardContainer)` | ||
flex-direction: row; | ||
justify-content: center; | ||
align-items: baseline; | ||
gap: 32px; | ||
padding: 40px 0px; | ||
@media (min-width: 800px) and (max-width: 855px) { | ||
padding: 40px 24px; | ||
gap: 24px; | ||
} | ||
@media (max-width: 800px) { | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
`; | ||
|
||
const StyledButton = styled.a` | ||
display: flex; | ||
flex-direction: row; | ||
font-size: ${Theme.font.size.lg}; | ||
font-weight: ${Theme.font.weight.medium}; | ||
padding: 14px 24px; | ||
color: ${Theme.text.color.primary}; | ||
font-family: var(--font-gabarito); | ||
background-color: #fafafa; | ||
border: 2px solid ${Theme.color.gray60}; | ||
border-radius: 12px; | ||
gap: 12px; | ||
cursor: pointer; | ||
text-decoration: none; | ||
box-shadow: | ||
-6px 6px 0px 1px #fafafa, | ||
-6px 6px 0px 3px ${Theme.color.gray60}; | ||
&:hover { | ||
box-shadow: -6px 6px 0px 1px ${Theme.color.gray60}; | ||
} | ||
`; | ||
|
||
interface ProfileProps { | ||
userUrl: string; | ||
username: string; | ||
} | ||
|
||
export const ProfileSharing = ({ userUrl, username }: ProfileProps) => { | ||
const contributorUrl = `${process.env.NEXT_PUBLIC_HOST_URL}/contributors/${username}`; | ||
|
||
const handleDownload = async () => { | ||
const imageSrc = `${process.env.NEXT_PUBLIC_HOST_URL}/api/contributors/og-image/${username}`; | ||
try { | ||
const response = await fetch(imageSrc); | ||
const blob = await response.blob(); | ||
const url = window.URL.createObjectURL(blob); | ||
|
||
const a = document.createElement('a'); | ||
a.href = url; | ||
a.download = `twenty-${username}.png`; | ||
document.body.appendChild(a); | ||
a.click(); | ||
a.remove(); | ||
window.URL.revokeObjectURL(url); | ||
} catch (error) { | ||
console.error('Error downloading the image:', error); | ||
} | ||
}; | ||
|
||
return ( | ||
<Container> | ||
<StyledButton href={userUrl} target="blank"> | ||
<GithubIcon color="black" size="24px" /> | ||
Visit Profile | ||
</StyledButton> | ||
<StyledButton onClick={handleDownload}> | ||
<IconDownload /> Download Image | ||
</StyledButton> | ||
<StyledButton | ||
href={`http://www.twitter.com/share?url=${contributorUrl}`} | ||
target="blank" | ||
> | ||
<XIcon color="black" size="24px" /> Share on X | ||
</StyledButton> | ||
</Container> | ||
); | ||
}; |
131 changes: 131 additions & 0 deletions
131
packages/twenty-website/src/app/api/contributors/og-image/[slug]/route.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { format } from 'date-fns'; | ||
import { ImageResponse } from 'next/og'; | ||
|
||
import { | ||
bottomBackgroundImage, | ||
container, | ||
contributorInfo, | ||
contributorInfoBox, | ||
contributorInfoContainer, | ||
contributorInfoStats, | ||
contributorInfoTitle, | ||
infoSeparator, | ||
profileContainer, | ||
profileContributionHeader, | ||
profileInfoContainer, | ||
profileUsernameHeader, | ||
styledContributorAvatar, | ||
topBackgroundImage, | ||
} from '@/app/api/contributors/og-image/[slug]/style'; | ||
import { getContributorActivity } from '@/app/contributors/utils/get-contributor-activity'; | ||
|
||
const GABARITO_FONT_CDN_URL = | ||
'https://fonts.cdnfonts.com/s/105143/Gabarito-Medium-BF651cdf1f3f18e.woff'; | ||
|
||
const getGabarito = async () => { | ||
const fontGabarito = await fetch(GABARITO_FONT_CDN_URL).then((res) => | ||
res.arrayBuffer(), | ||
); | ||
|
||
return fontGabarito; | ||
}; | ||
|
||
export async function GET(request: Request) { | ||
try { | ||
const url = request.url; | ||
|
||
const username = url.split('/')?.pop() || ''; | ||
|
||
const contributorActivity = await getContributorActivity(username); | ||
if (contributorActivity) { | ||
const { | ||
firstContributionAt, | ||
mergedPRsCount, | ||
rank, | ||
activeDays, | ||
contributorAvatar, | ||
} = contributorActivity; | ||
return await new ImageResponse( | ||
( | ||
<div style={container}> | ||
<div style={topBackgroundImage}></div> | ||
<div style={bottomBackgroundImage}></div> | ||
<div style={profileContainer}> | ||
<img src={contributorAvatar} style={styledContributorAvatar} /> | ||
<div style={profileInfoContainer}> | ||
<h1 style={profileUsernameHeader}>@{username} x Twenty</h1> | ||
<h2 style={profileContributionHeader}> | ||
Since {format(new Date(firstContributionAt), 'MMMM yyyy')} | ||
</h2> | ||
</div> | ||
<svg | ||
width="96" | ||
height="96" | ||
viewBox="0 0 136 136" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<g clip-path="url(#clip0_2343_96406)"> | ||
<path | ||
d="M136 2.28882e-05H0L0.000144482 136H136V2.28882e-05ZM27.27 50.6401C27.27 43.2101 33.3 37.1801 40.73 37.1801H66.64C67.02 37.1801 67.37 37.4101 67.53 37.7601C67.69 38.1101 67.62 38.5201 67.36 38.8101L61.68 44.9801C60.69 46.0501 59.3 46.6701 57.84 46.6701H40.8C38.57 46.6701 36.76 48.4801 36.76 50.7101V60.8901C36.76 62.2001 35.7 63.2601 34.39 63.2601H29.65C28.34 63.2601 27.28 62.2001 27.28 60.8901V50.6401H27.27ZM107.88 85.3601C107.88 92.7901 101.85 98.82 94.42 98.82H83.41C75.98 98.82 69.95 92.7901 69.95 85.3601V66.0901C69.95 64.7801 70.44 63.5201 71.33 62.5501L77.75 55.5801C78.02 55.2901 78.44 55.1901 78.82 55.3301C79.19 55.4801 79.44 55.83 79.44 56.23V85.3001C79.44 87.5301 81.25 89.3401 83.48 89.3401H94.36C96.59 89.3401 98.4 87.5301 98.4 85.3001V50.7101C98.4 48.4801 96.59 46.6701 94.36 46.6701H81.71C80.26 46.6701 78.88 47.2801 77.89 48.3401L40.16 89.3401H62.83C64.14 89.3401 65.2 90.4001 65.2 91.7101V96.4501C65.2 97.7601 64.14 98.82 62.83 98.82H32.28C29.51 98.82 27.26 96.5701 27.26 93.8001V91.29C27.26 90.03 27.73 88.8201 28.59 87.8901L70.89 41.9401C73.69 38.9001 77.62 37.1801 81.75 37.1801H94.41C101.84 37.1801 107.87 43.2101 107.87 50.6401V85.3601H107.88Z" | ||
fill="black" | ||
/> | ||
<path | ||
d="M27.27 50.6401C27.27 43.2101 33.3 37.1801 40.73 37.1801H66.64C67.02 37.1801 67.37 37.4101 67.53 37.7601C67.69 38.1101 67.62 38.5201 67.36 38.8101L61.68 44.9801C60.69 46.0501 59.3 46.6701 57.84 46.6701H40.8C38.57 46.6701 36.76 48.4801 36.76 50.7101V60.8901C36.76 62.2001 35.7 63.2601 34.39 63.2601H29.65C28.34 63.2601 27.28 62.2001 27.28 60.8901V50.6401H27.27Z" | ||
fill="white" | ||
/> | ||
<path | ||
d="M107.88 85.3601C107.88 92.7901 101.85 98.82 94.42 98.82H83.41C75.98 98.82 69.95 92.7901 69.95 85.3601V66.0901C69.95 64.7801 70.44 63.5201 71.33 62.5501L77.75 55.5801C78.02 55.2901 78.44 55.1901 78.82 55.3301C79.19 55.4801 79.44 55.83 79.44 56.23V85.3001C79.44 87.5301 81.25 89.3401 83.48 89.3401H94.36C96.59 89.3401 98.4 87.5301 98.4 85.3001V50.7101C98.4 48.4801 96.59 46.6701 94.36 46.6701H81.71C80.26 46.6701 78.88 47.2801 77.89 48.3401L40.16 89.3401H62.83C64.14 89.3401 65.2 90.4001 65.2 91.7101V96.4501C65.2 97.7601 64.14 98.82 62.83 98.82H32.28C29.51 98.82 27.26 96.5701 27.26 93.8001V91.29C27.26 90.03 27.73 88.8201 28.59 87.8901L70.89 41.9401C73.69 38.9001 77.62 37.1801 81.75 37.1801H94.41C101.84 37.1801 107.87 43.2101 107.87 50.6401V85.3601H107.88Z" | ||
fill="white" | ||
/> | ||
</g> | ||
<defs> | ||
<clipPath id="clip0_2343_96406"> | ||
<rect width="136" height="136" rx="16" fill="white" /> | ||
</clipPath> | ||
</defs> | ||
</svg> | ||
</div> | ||
<div style={contributorInfoContainer}> | ||
<div style={contributorInfoBox}> | ||
<div style={contributorInfo}> | ||
<h3 style={contributorInfoTitle}>Merged PR</h3> | ||
<p style={contributorInfoStats}>{mergedPRsCount}</p> | ||
</div> | ||
<div style={infoSeparator} /> | ||
</div> | ||
<div style={contributorInfoBox}> | ||
<div style={contributorInfo}> | ||
<h3 style={contributorInfoTitle}>Ranking</h3> | ||
<p style={contributorInfoStats}>{rank}%</p> | ||
</div> | ||
<div style={infoSeparator} /> | ||
</div> | ||
<div style={contributorInfoBox}> | ||
<div style={contributorInfo}> | ||
<h3 style={contributorInfoTitle}>Active Days</h3> | ||
<h1 style={contributorInfoStats}>{activeDays}</h1> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
), | ||
{ | ||
width: 1200, | ||
height: 630, | ||
fonts: [ | ||
{ | ||
name: 'Gabarito', | ||
data: await getGabarito(), | ||
style: 'normal', | ||
}, | ||
], | ||
}, | ||
); | ||
} | ||
} catch (error) { | ||
return new Response(`error: ${error}`, { | ||
status: 500, | ||
}); | ||
} | ||
} |
130 changes: 130 additions & 0 deletions
130
packages/twenty-website/src/app/api/contributors/og-image/[slug]/style.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { CSSProperties } from 'react'; | ||
|
||
const BACKGROUND_IMAGE_URL = | ||
'https://framerusercontent.com/images/nqEmdwe7yDXNsOZovuxG5zvj2E.png'; | ||
|
||
export const container: CSSProperties = { | ||
display: 'flex', | ||
flexDirection: 'column', | ||
width: '1200px', | ||
height: '630px', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
backgroundColor: 'white', | ||
fontFamily: 'Gabarito', | ||
}; | ||
|
||
export const topBackgroundImage: CSSProperties = { | ||
backgroundImage: `url(${BACKGROUND_IMAGE_URL})`, | ||
position: 'absolute', | ||
zIndex: '-1', | ||
width: '1300px', | ||
height: '250px', | ||
transform: 'rotate(-11deg)', | ||
opacity: '0.2', | ||
top: '-100', | ||
left: '-25', | ||
}; | ||
|
||
export const bottomBackgroundImage: CSSProperties = { | ||
backgroundImage: `url(${BACKGROUND_IMAGE_URL})`, | ||
position: 'absolute', | ||
zIndex: '-1', | ||
width: '1300px', | ||
height: '250px', | ||
transform: 'rotate(-11deg)', | ||
opacity: '0.2', | ||
bottom: '-120', | ||
right: '-40', | ||
}; | ||
|
||
export const profileContainer: CSSProperties = { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
justifyContent: 'space-between', | ||
width: '780px', | ||
margin: '0px 0px 40px', | ||
}; | ||
|
||
export const styledContributorAvatar = { | ||
display: 'flex', | ||
width: '96px', | ||
height: '96px', | ||
margin: '0px', | ||
border: '3px solid #141414', | ||
borderRadius: '16px', | ||
}; | ||
|
||
export const profileInfoContainer: CSSProperties = { | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: '8px', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}; | ||
|
||
export const profileUsernameHeader: CSSProperties = { | ||
margin: '0px', | ||
fontSize: '28px', | ||
fontWeight: '700', | ||
color: '#141414', | ||
fontFamily: 'Gabarito', | ||
}; | ||
|
||
export const profileContributionHeader: CSSProperties = { | ||
margin: '0px', | ||
color: '#818181', | ||
fontSize: '20px', | ||
fontWeight: '400', | ||
}; | ||
|
||
export const contributorInfoContainer: CSSProperties = { | ||
border: '3px solid #141414', | ||
borderRadius: '12px', | ||
display: 'flex', | ||
flexDirection: 'row', | ||
justifyContent: 'space-around', | ||
width: '780px', | ||
height: '149px', | ||
backgroundColor: '#F1F1F1', | ||
}; | ||
|
||
export const contributorInfoBox: CSSProperties = { | ||
flex: 1, | ||
display: 'flex', | ||
flexDirection: 'row', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
position: 'relative', | ||
}; | ||
|
||
export const contributorInfo: CSSProperties = { | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
margin: '32px', | ||
gap: '16px', | ||
}; | ||
|
||
export const contributorInfoTitle = { | ||
color: '#B3B3B3', | ||
margin: '0px', | ||
fontWeight: '500', | ||
fontSize: '24px', | ||
}; | ||
|
||
export const contributorInfoStats = { | ||
color: '#474747', | ||
margin: '0px', | ||
fontWeight: '700', | ||
fontSize: '40px', | ||
}; | ||
|
||
export const infoSeparator: CSSProperties = { | ||
position: 'absolute', | ||
right: 0, | ||
display: 'flex', | ||
width: '2px', | ||
height: '85px', | ||
backgroundColor: '#141414', | ||
}; |
Oops, something went wrong.