Skip to content
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c00c218
feat: community layout & board page
aminoxix Jun 4, 2025
55425bb
chore: update tools.json (#4117)
asyncapi-bot May 19, 2025
fc1bdda
docs(community): update latest maintainers list (#4118)
asyncapi-bot May 19, 2025
4c8afd1
docs(community): update latest maintainers list (#4120)
asyncapi-bot May 19, 2025
a5edb9d
chore: update meetings.json and newsrooom_videos.json (#4121)
asyncapi-bot May 20, 2025
ea7aa48
chore: update meetings.json and newsrooom_videos.json (#4123)
asyncapi-bot May 21, 2025
0ab497f
chore: update meetings.json and newsrooom_videos.json (#4124)
asyncapi-bot May 23, 2025
31a378d
docs(community): update latest maintainers list (#4119)
asyncapi-bot May 24, 2025
3b51264
fix: misaligned-nav-box (#4094)
itzraghavv May 24, 2025
c4aeee7
fix: redesign 'Latest News' section in Community Newsroom (#3982)
PankajKumardev May 24, 2025
4f5fe34
fix: remove console logs exposing user data and critical website flow…
sagarkori143 May 24, 2025
871910f
fix: missing Hero/Banner on Case Study Pages (#4103)
noogler-eng May 24, 2025
b2b77c1
fix: updated few outdated information (#3723)
Adi-204 May 24, 2025
2879613
chore: update meetings.json and newsrooom_videos.json (#4125)
asyncapi-bot May 25, 2025
4173294
chore: update tools.json (#4129)
asyncapi-bot May 26, 2025
b5e4c06
chore(blog): add singapore summary (#4056)
iambami May 26, 2025
8b2aa60
ci: update of files from global .github repo (#4130)
asyncapi-bot May 26, 2025
3dbc4ab
chore(blog): add marketing q1 report (#3911)
iambami May 27, 2025
d956b2b
docs(community): update latest tsc members list (#4135)
asyncapi-bot May 28, 2025
ee748e0
docs(generator): update latest generator documentation (#4136)
asyncapi-bot May 28, 2025
d0b770e
chore(blog): add google summer of 2025 first phase summary (#4131)
AceTheCreator May 28, 2025
2050695
chore: update meetings.json and newsrooom_videos.json (#4137)
asyncapi-bot May 29, 2025
857b17f
chore(blog): add may update (#4132)
thulieblack May 29, 2025
8417bcc
fix: update eslintrc for all types of files (#4128)
akshatnema May 29, 2025
99cc9b0
chore: update meetings.json and newsrooom_videos.json (#4139)
asyncapi-bot May 30, 2025
d098c74
chore: update meetings.json and newsrooom_videos.json (#4140)
asyncapi-bot May 31, 2025
b0490f0
chore: update meetings.json and newsrooom_videos.json (#4141)
asyncapi-bot Jun 1, 2025
3756243
chore: update tools.json (#4142)
asyncapi-bot Jun 2, 2025
77e4680
docs(community): update latest Board and TSC members list (#4144)
asyncapi-bot Jun 2, 2025
80ff0c3
docs(community): update latest community documentation (#4146)
asyncapi-bot Jun 3, 2025
4d2851b
docs(generator): update latest generator documentation (#4147)
asyncapi-bot Jun 3, 2025
fcce3f6
chore: update meetings.json and newsrooom_videos.json (#4149)
asyncapi-bot Jun 4, 2025
25566f7
Merge remote-tracking branch 'origin' into feat/community-page
aminoxix Jun 4, 2025
77fad43
fix: relevantMembersList type, TSC_BOARD_MEMBERS.yaml removed as JSON…
aminoxix Jun 4, 2025
e86178b
Merge pull request #20 from asyncapi/master
aminoxix Jun 6, 2025
4100d17
Merge branch 'master' into feat/community-page
asyncapi-bot Jun 6, 2025
e75193b
feat: baord members navigation menu, scrollable flyout menu & fallbac…
aminoxix Jun 7, 2025
8053b66
fix: redirected to dedicated ambassador page based on github username
aminoxix Jun 8, 2025
381f0c7
Merge branch 'master' into feat/community-page
derberg Jun 10, 2025
11bd0b4
Merge pull request #21 from asyncapi/master
aminoxix Jun 10, 2025
46ed7ff
Merge branch 'master' into feat/community-page
aminoxix Jun 10, 2025
2828eea
fix: AsyncAPI ambassador hyperlink styling
aminoxix Jun 10, 2025
c4cfbf8
Merge branch 'master' into feat/community-page
asyncapi-bot Jun 12, 2025
fa26bfc
feat: badge for chairperson
aminoxix Jun 12, 2025
5f7274f
Merge branch 'master' into feat/community-page
aminoxix Jun 16, 2025
e6cbd83
Merge branch 'master' into feat/community-page
aminoxix Jul 7, 2025
d572f5a
fix: tsc board members json updated
aminoxix Jul 7, 2025
004f928
Apply suggestions from code review
derberg Jul 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/CaseStudyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) {
}

return (
<div className='flex flex-wrap gap-3 pt-10 justify-center lg:gap-8 lg:text-center'>
<div className='flex flex-wrap justify-center gap-3 pt-10 lg:gap-8 lg:text-center'>
{studies.map((study, index) => (
<a key={index} className='lg:w-[30%]' href={`casestudies/${study.id}`}>
<div
Expand Down
318 changes: 318 additions & 0 deletions components/layout/CommunityLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
import { sortBy } from 'lodash';
import React from 'react';

import type { Ambassador, Tsc } from '@/types/pages/community/Community';

import tscBoardList from '../../config/TSC_BOARD_MEMBERS.json';
import IconGithub from '../icons/Github';
import IconLinkedIn from '../icons/LinkedIn';
import IconTwitter from '../icons/Twitter';
import NewsletterSubscribe from '../NewsletterSubscribe';
import TextLink from '../typography/TextLink';
import GenericLayout from './GenericLayout';

interface SocialLinkProps {
href: string;
social: string;
}

interface TSCUser {
user: Tsc | Ambassador;
}

/**
* @description This function adds additional information to the user object having Board (TSC or Ambassador) data.
*
* @param user The user object having Board (TSC or Ambassador) data.
* @returns The user object with additional information.
*/
function addAdditionalUserInfo(user: Tsc | Ambassador) {
const userData: Tsc | Ambassador = {
...user
};

// if username is not present, use the github username
if (!userData.name) {
userData.name = userData.github;
}

// add social links
if (userData.github) {
userData.github = `https://www.github.com/${userData.github}`;
}
if (userData.linkedin) {
userData.linkedin = `https://www.linkedin.com/in/${userData.linkedin}`;
}
if (userData.twitter) {
userData.twitter = `https://www.twitter.com/${userData.twitter}`;
}

// add avatar url
// github redirects to avatar url using `https://www.github.com/<username>.png`
userData.avatarUrl = `${userData.github}.png`;

// make repo links
if ('repos' in userData) {
userData.repos = userData.repos.map((repoName: string) => ({
name: repoName,
url: `https://www.github.com/asyncapi/${repoName}`
}));
}

return userData;
}

/**
* @description This function returns the SVG component for Twitter.
*
* @returns The Twitter SVG component.
*/
function TwitterSVG() {
return (
<div className='size-5'>
<IconTwitter className='hover:fill-black' />
</div>
);
}

/**
* @description This function returns the SVG component for GitHub.
*
* @returns The GitHub SVG component.
*/
function GitHubSVG() {
return (
<div className='size-5'>
<IconGithub className='hover:fill-black' />
</div>
);
}

/**
* @description This function returns the SVG component for LinkedIn.
*
* @returns The LinkedIn SVG component.
*/
function LinkedInSVG() {
return (
<div className='size-5'>
<IconLinkedIn className='hover:fill-linkedin' />
</div>
);
}

const socials: { [key: string]: React.JSX.Element } = {
GitHub: <GitHubSVG />,
Twitter: <TwitterSVG />,
Linkedin: <LinkedInSVG />
};

/**
* @description This function returns the social link component.
*
* @param {SocialLinkProps} props - The props for the social link component.
* @param {string} props.href - The URL for the social link.
* @param {string} props.social - The social media platform.
*/
function SocialLink({ href, social }: SocialLinkProps) {
return (
<li>
<a
data-testid='Social-Links'
href={href}
className='text-gray-600 hover:text-gray-500'
target='_blank'
rel='noreferrer noopener'
>
<span className='sr-only'>{social}</span>
{socials[social]}
</a>
</li>
);
}

/**
* @description This function returns the user work status component.
*
* @param {TSCUser} props - The props for the user work status component.
* @param {Tsc | Ambassador} props.user - The user object having Board (TSC or Ambassador) data.
*/
function UserWorkStatus({ user }: TSCUser) {
if ('availableForHire' in user && user.availableForHire) {
return (
<div
data-testid='status-element'
className='text-md inline-flex items-center rounded-full bg-green-100 px-3 py-1 font-medium leading-5 text-green-800'
>
Available for hire
</div>
);
}
if (user.company) {
return (
<div
data-testid='status-element'
className='text-md inline-flex items-center rounded-full bg-orange-100 px-3 py-1 font-medium leading-5 text-orange-800'
>
{user.company}
</div>
);
}

return (
<div
data-testid='status-element'
className='text-md inline-flex items-center rounded-full bg-blue-100 px-3 py-1 font-medium leading-5 text-blue-800'
>
Individual Member
</div>
);
}

/**
* @description This function returns the user info component.
*
* @param {TSCUser} props - The props for the user info component.
* @param {Tsc} props.user - The user object having Board (TSC or Ambassador) data.
*/
function UserInfo({ user }: TSCUser) {
const githubUsername = user.github.split('/').pop();

return (
<li
data-testid='UserInfo-list'
className='rounded-md border border-gray-200 p-4 text-center shadow-md'
key={user.github}
>
<div className='flex flex-row'>
<img
data-testid='UserInfo-avatar'
src={user.avatarUrl}
alt={user.name}
className='mx-auto size-20 rounded-full xl:size-28'
/>
<div className='flex-1'>
<div className='my-3 text-lg font-bold' data-testid='UserInfo-name'>
{user.name}
</div>
<UserWorkStatus user={user} />
<ul role='list' className='my-5 flex justify-center space-x-5'>
<SocialLink href={user.github} social='GitHub' />
{user.twitter ? <SocialLink href={user.twitter} social='Twitter' /> : null}
{user.linkedin ? <SocialLink href={user.linkedin} social='Linkedin' /> : null}
</ul>
</div>
</div>
{'repos' in user ? (
<div className='flex flex-wrap items-center gap-1'>
Maintainer of:
{user.repos.map((repo: { name: string; url: string }) => (
<a
data-testid='Repo-Links'
key={repo.name}
className='inline-flex items-center rounded-full bg-cyan-100 px-3 py-0.5 text-xs font-medium leading-5 text-cyan-800 hover:bg-cyan-300'
href={repo.url}
>
{repo.name}
</a>
))}
</div>
) : (
// fallback to ambassador page
<TextLink
href={`/community/ambassadors/${githubUsername}`}
className='flex font-normal text-base text-blue-500 no-underline hover:text-sky-400'
>
AsyncAPI Ambassador
</TextLink>
)}
</li>
);
}

/**
* @description This function returns the question card component.
*/
function QuestionCard() {
return (
<li className='rounded-md border border-gray-200 p-4 px-6 py-10 text-center shadow-md' data-testid='Question-card'>
<img
data-testid='Question-card-img'
src='/img/avatars/questionmark.webp'
alt='Question Mark'
className='mx-auto size-20 rounded-full xl:size-28'
/>
<div className='my-4'>
Want to become a member? Follow this
<TextLink
href='https://github.com/asyncapi/community/blob/master/TSC_MEMBERSHIP.md'
target='_blank'
className='font-normal text-base text-sky-600 no-underline hover:text-sky-400'
>
Link
</TextLink>
&nbsp;to know more!
</div>
</li>
);
}

export enum Membership {
TSC = 'TSC',
BOARD = 'Board'
}

interface ICommunityLayout {
membership: Membership;
children: React.ReactNode;
}

/**
* @description This function returns the TSC or Board component.
* @param {Membership} props.membership - determines the community members belong to board or TSC (ambassadors & maintainers).
*/
export default function CommunityLayout({ children, membership }: ICommunityLayout) {
const description = `Meet the current AsyncAPI ${membership} members and learn how you can become one.`;
const image = `/img/social/community-${membership.toLowerCase()}.webp`;

const isTSCMembership = membership === Membership.TSC;
const tscBoardMembers = sortBy(
tscBoardList.map((user) => addAdditionalUserInfo(user)),
['name']
).filter((user) => (isTSCMembership ? user.isTscMember : user.isBoardMember || user.isBoardChair));

return (
<GenericLayout
title={isTSCMembership ? 'Technical Steering Committee' : 'Board Committee'}
description={description}
image={image}
wide
>
<div className='relative mx-auto px-4 py-12 sm:px-6 lg:px-8'>
{children}
{isTSCMembership && (
<div className='mt-8'>
<NewsletterSubscribe
type='TSC Voting'
title='Get notified when TSC is voting'
subtitle="You'll receive an email whenever someone requests the TSC to vote."
/>
</div>
)}
<div className='mt-10'>
<div className='text-primary-800 mb-5 text-center'>
<h3 className='text-2xl font-semibold'>Current {membership} members</h3>
<span className='font-thin text-sm'>(in alphabetical order)</span>
</div>

<ul role='list' className='space-y-4 sm:grid sm:grid-cols-2 sm:gap-6 sm:space-y-0 lg:grid-cols-3 lg:gap-8'>
{tscBoardMembers.map((user) => (
<UserInfo key={user.github} user={user} />
))}
<QuestionCard />
</ul>
</div>
</div>
</GenericLayout>
);
}
2 changes: 1 addition & 1 deletion components/navigation/FlyoutMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface FlyoutProps {
export default function Flyout({ items = [] }: FlyoutProps) {
return (
<div
className='absolute z-50 -ml-4 w-screen max-w-md pt-3 md:ml-12 md:-translate-x-1/2 lg:left-1/2 lg:max-w-3xl lg:-translate-x-1/2'
className='absolute z-50 -ml-4 md:h-[80vh] overflow-x-scroll w-screen max-w-md pt-3 md:ml-12 md:-translate-x-1/2 lg:left-1/2 lg:max-w-3xl lg:-translate-x-1/2'
data-testid='Flyout-main'
>
<div className='rounded-lg shadow-lg'>
Expand Down
7 changes: 7 additions & 0 deletions components/navigation/communityItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import IconContributing from '../icons/Contributing';
import IconDashboard from '../icons/Dashboard';
import IconGithubOrganization from '../icons/GithubOrganization';
import IconMeetings from '../icons/Meetings';
import IconModelina from '../icons/Modelina';
import IconNewsroom from '../icons/Newsroom';
import IconSlack from '../icons/Slack';
import IconTSC from '../icons/TSC';
Expand Down Expand Up @@ -52,6 +53,12 @@ const communityItems: CommunityItem[] = [
href: '/community/ambassadors',
description: 'Passionate about APIs? Become an AsyncAPI Ambassador and help shape the future of APIs.'
},
{
icon: IconModelina,
title: 'Board Members',
href: '/community/board',
description: 'Get to know what is a Board member, how you can become one, and meet our current board members.'
},
{
icon: IconDashboard,
title: 'Dashboard',
Expand Down
Loading
Loading