@ -0,0 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { ResponsiveTimeRange } from '@nivo/calendar';
|
||||
|
||||
import { CardContainer } from '@/app/_components/contributors/CardContainer';
|
||||
import { Title } from '@/app/_components/contributors/Title';
|
||||
|
||||
export const ActivityLog = ({
|
||||
data,
|
||||
}: {
|
||||
data: { value: number; day: string }[];
|
||||
}) => {
|
||||
if (!data.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CardContainer>
|
||||
<Title>Activity</Title>
|
||||
<div style={{ width: '100%', height: '214px' }}>
|
||||
<ResponsiveTimeRange
|
||||
data={data}
|
||||
emptyColor="#F4EFFF"
|
||||
colors={['#E9DFFF', '#B28FFE', '#915FFD']}
|
||||
dayBorderWidth={2}
|
||||
dayBorderColor="#ffffff"
|
||||
dayRadius={4}
|
||||
daySpacing={2}
|
||||
/>
|
||||
</div>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import Link from 'next/link';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
const AvatarGridContainer = styled.div`
|
||||
margin: 0 auto;
|
||||
max-width: 1024px;
|
||||
justify-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const AvatarItem = styled.div`
|
||||
position: relative;
|
||||
width: 124px;
|
||||
height: 124px;
|
||||
border: 3px solid #141414;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
transition: 200ms;
|
||||
|
||||
&:hover {
|
||||
-webkit-box-shadow: -6px 6px 0px 1px rgba(0, 0, 0, 1);
|
||||
-moz-box-shadow: -6px 6px 0px 1px rgba(0, 0, 0, 1);
|
||||
box-shadow: -6px 6px 0px 1px rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.username {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
text-align: center;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
visibility 0.3s;
|
||||
}
|
||||
|
||||
&:hover .username {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const AvatarGrid = ({ users }: { users: User[] }) => {
|
||||
return (
|
||||
<AvatarGridContainer>
|
||||
{users.map((user) => (
|
||||
<Link href={`/contributors/${user.id}`} key={`l_${user.id}`}>
|
||||
<AvatarItem key={user.id}>
|
||||
<img src={user.avatarUrl} alt={user.id} />
|
||||
<span className="username">{user.id}</span>
|
||||
</AvatarItem>
|
||||
</Link>
|
||||
))}
|
||||
</AvatarGridContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvatarGrid;
|
||||
@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { Breadcrumbs } from '@/app/_components/ui/layout/Breadcrumbs';
|
||||
|
||||
const BREADCRUMB_ITEMS = [
|
||||
{
|
||||
uri: '/contributors',
|
||||
label: 'Contributors',
|
||||
},
|
||||
];
|
||||
|
||||
export const Breadcrumb = ({ active }: { active: string }) => {
|
||||
return (
|
||||
<Breadcrumbs items={BREADCRUMB_ITEMS} activePage={active} separator="/" />
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const CardContainer = styled.div`
|
||||
border: 3px solid #141414;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
flex-direction: column;
|
||||
background-color: #fafafa;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
max-width: 898px;
|
||||
padding: 40px;
|
||||
|
||||
gap: 40px;
|
||||
@media (max-width: 809px) {
|
||||
width: 100%;
|
||||
padding: 40px 24px 40px 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ContentContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
return <Container>{children}</Container>;
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const Title = styled.h2`
|
||||
font-size: 56px;
|
||||
font-weight: 600;
|
||||
color: #b3b3b3;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 64px;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 28px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<>
|
||||
<Title>
|
||||
Our amazing <br /> <span style={{ color: 'black' }}>Contributors</span>
|
||||
</Title>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { GithubIcon } from '@/app/_components/ui/icons/SvgIcons';
|
||||
|
||||
const ProfileContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Avatar = styled.div`
|
||||
border: 3px solid #141414;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
`;
|
||||
|
||||
const Details = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.username {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
line-height: 48px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 24px;
|
||||
line-height: 28.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.duration {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: #818181;
|
||||
margin: 0;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledGithubIcon = styled(GithubIcon)`
|
||||
@media (max-width: 810px) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProfileCardProps {
|
||||
username: string;
|
||||
avatarUrl: string;
|
||||
firstContributionAt: string;
|
||||
}
|
||||
|
||||
export const ProfileCard = ({
|
||||
username,
|
||||
avatarUrl,
|
||||
firstContributionAt,
|
||||
}: ProfileCardProps) => {
|
||||
return (
|
||||
<ProfileContainer>
|
||||
<Avatar>
|
||||
<img src={avatarUrl} alt={username} />
|
||||
</Avatar>
|
||||
<Details>
|
||||
<h3 className="username">
|
||||
@{username}
|
||||
<a href={`https://github.com/${username}`} target="_blank">
|
||||
<StyledGithubIcon size="M" color="rgba(0,0,0,1)" />
|
||||
</a>
|
||||
</h3>
|
||||
{firstContributionAt && (
|
||||
<p className="duration">
|
||||
Contributing since{' '}
|
||||
{format(new Date(firstContributionAt), 'MMMM yyyy')}
|
||||
</p>
|
||||
)}
|
||||
</Details>
|
||||
</ProfileContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CardContainer } from '@/app/_components/contributors/CardContainer';
|
||||
|
||||
const Container = styled(CardContainer)`
|
||||
flex-direction: row;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
color: #b3b3b3;
|
||||
margin: 0;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
color: #474747;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 2px;
|
||||
background-color: #141414;
|
||||
border-radius: 40px;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProfileInfoProps {
|
||||
mergedPRsCount: number;
|
||||
rank: string;
|
||||
activeDays: number;
|
||||
}
|
||||
|
||||
export const ProfileInfo = ({
|
||||
mergedPRsCount,
|
||||
rank,
|
||||
activeDays,
|
||||
}: ProfileInfoProps) => {
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<div className="item">
|
||||
<p className="title">Merged PR</p>
|
||||
<span className="value">{mergedPRsCount}</span>
|
||||
</div>
|
||||
<div className="separator"></div>
|
||||
<div className="item">
|
||||
<p className="title">Rank</p>
|
||||
<span className="value">{rank}%</span>
|
||||
</div>
|
||||
<div className="separator"></div>
|
||||
<div className="item">
|
||||
<p className="title">Active Days</p>
|
||||
<span className="value">{activeDays}</span>
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,109 @@
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import styled from '@emotion/styled';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { PullRequestIcon } from '@/app/_components/ui/icons/SvgIcons';
|
||||
import { formatIntoRelativeDate } from '@/shared-utils/formatIntoRelativeDate';
|
||||
|
||||
const StyledTooltip = styled(Tooltip)``;
|
||||
|
||||
const Item = styled.div`
|
||||
display: flex;
|
||||
gap: 17px;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.a`
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: #474747;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPrLink = styled.a`
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: #474747;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 500;
|
||||
color: #b3b3b3;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPullRequestIcon = styled(PullRequestIcon)`
|
||||
@media screen {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface PullRequestItemProps {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
mergedAt: string | null;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export const PullRequestItem = ({
|
||||
id,
|
||||
title,
|
||||
url,
|
||||
createdAt,
|
||||
mergedAt,
|
||||
authorId,
|
||||
}: PullRequestItemProps) => {
|
||||
const prNumber = url.split('/').slice(-1)[0];
|
||||
return (
|
||||
<Item key={id}>
|
||||
<div>
|
||||
<StyledPullRequestIcon
|
||||
color={mergedAt ? '#915FFD' : '#1A7F37'}
|
||||
size="M"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<StyledTitle href={url} target="_blank">
|
||||
{title}
|
||||
</StyledTitle>
|
||||
<StyledDescription>
|
||||
<StyledPrLink
|
||||
href={'https://github.com/twentyhq/twenty/pull/' + prNumber}
|
||||
target="__blank"
|
||||
>
|
||||
#{prNumber}
|
||||
</StyledPrLink>{' '}
|
||||
by {authorId} was {mergedAt ? `merged` : `opened`}{' '}
|
||||
<span id={`date-${prNumber}`}>
|
||||
{formatIntoRelativeDate(mergedAt ? mergedAt : createdAt)}
|
||||
</span>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#date-${prNumber}`}
|
||||
content={format(
|
||||
new Date(mergedAt ? mergedAt : createdAt),
|
||||
'dd MMMM yyyy',
|
||||
)}
|
||||
clickable
|
||||
noArrow
|
||||
/>
|
||||
</StyledDescription>
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CardContainer } from '@/app/_components/contributors/CardContainer';
|
||||
import { PullRequestItem } from '@/app/_components/contributors/PullRequestItem';
|
||||
import { Title } from '@/app/_components/contributors/Title';
|
||||
|
||||
const List = styled.div`
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-direction: column;
|
||||
`;
|
||||
interface PullRequestsProps {
|
||||
list: {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
mergedAt: string | null;
|
||||
authorId: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const PullRequests = ({ list }: PullRequestsProps) => {
|
||||
return (
|
||||
<CardContainer>
|
||||
<Title>Pull Requests</Title>
|
||||
<List>
|
||||
{list.map((pr) => (
|
||||
<PullRequestItem
|
||||
key={pr.id}
|
||||
id={pr.id}
|
||||
title={pr.title}
|
||||
url={pr.url}
|
||||
createdAt={pr.createdAt}
|
||||
mergedAt={pr.mergedAt}
|
||||
authorId={pr.authorId}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CardContainer } from '@/app/_components/contributors/CardContainer';
|
||||
import { HeartIcon } from '@/app/_components/ui/icons/SvgIcons';
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHeartIcon = styled(HeartIcon)`
|
||||
@media (max-width: 810px) {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ThankYouProps {
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export const ThankYou = ({ authorId }: ThankYouProps) => {
|
||||
return (
|
||||
<CardContainer>
|
||||
<StyledTitle>
|
||||
Thank you @{authorId} <StyledHeartIcon color="#333333" size="18px" />
|
||||
</StyledTitle>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const Title = styled.h3`
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
line-height: 41.6px;
|
||||
font-weight: 500;
|
||||
|
||||
@media (max-width: 810px) {
|
||||
font-size: 24px;
|
||||
line-height: 31.2px;
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user