From 3ecba1c549887dd6e4e7a2ed8da0d82aa2d2c3e8 Mon Sep 17 00:00:00 2001 From: Shriya Chauhan <78415084+Shriya-Chauhan@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:37:16 +0530 Subject: [PATCH 01/41] Update if-nodejs-pr-testing.yml --- .github/workflows/if-nodejs-pr-testing.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 936ee8c42d36..18e58d886487 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -97,7 +97,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd with: header: markdown-check-error - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | ### Markdown Check Results @@ -114,7 +114,7 @@ jobs: with: header: markdown-check-error delete: true - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Run the test:locales script and capture output with cleaner formatting - if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' }} @@ -149,7 +149,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd with: header: locale-check-error - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | ### Locale Check Results We found issues with locale keys: @@ -165,7 +165,7 @@ jobs: with: header: locale-check-error delete: true - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: steps.packagejson.outputs.exists == 'true' name: Upload Coverage to Codecov From aebe7947a6dbe34a428f5a45f9cb9cd00656a218 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Wed, 23 Jul 2025 22:51:09 +0530 Subject: [PATCH 02/41] Revert "Update if-nodejs-pr-testing.yml" This reverts commit d54eec419e2ddadb341804adde0e9b5792f02914. --- .github/workflows/if-nodejs-pr-testing.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 18e58d886487..936ee8c42d36 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -97,7 +97,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd with: header: markdown-check-error - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} message: | ### Markdown Check Results @@ -114,7 +114,7 @@ jobs: with: header: markdown-check-error delete: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # Run the test:locales script and capture output with cleaner formatting - if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' }} @@ -149,7 +149,7 @@ jobs: uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd with: header: locale-check-error - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} message: | ### Locale Check Results We found issues with locale keys: @@ -165,7 +165,7 @@ jobs: with: header: locale-check-error delete: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - if: steps.packagejson.outputs.exists == 'true' name: Upload Coverage to Codecov From fa2ecf05eaee5bf8333ce6885f53b2f277451eb1 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Wed, 22 Oct 2025 11:42:34 +0530 Subject: [PATCH 03/41] feat: updated case studies page Signed-off-by: Shriya-Chauhan --- components/CaseStudyCard.tsx | 34 ++- pages/casestudies/index.tsx | 480 ++++++++++++++++++++++++++--------- styles/globals.css | 99 ++++++++ 3 files changed, 483 insertions(+), 130 deletions(-) diff --git a/components/CaseStudyCard.tsx b/components/CaseStudyCard.tsx index 035c4af723cc..de5026e57b55 100644 --- a/components/CaseStudyCard.tsx +++ b/components/CaseStudyCard.tsx @@ -1,8 +1,10 @@ +import Link from 'next/link'; import React from 'react'; import type { ICaseStudies } from '@/types/post'; import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; +import Button from './buttons/Button'; import Paragraph from './typography/Paragraph'; interface ICaseStudyCardProps { @@ -10,7 +12,7 @@ interface ICaseStudyCardProps { } /** - * @description A component that displays a list of case studies in a card format + * @description Enhanced case study card component with modern design * @param {ICaseStudies} props.studies - The list of case studies to display */ export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) { @@ -19,21 +21,29 @@ export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) { } return ( -
+
{studies.map((study, index) => ( - - ); diff --git a/pages/casestudies/index.tsx b/pages/casestudies/index.tsx index dace5cb0423f..12493822b321 100644 --- a/pages/casestudies/index.tsx +++ b/pages/casestudies/index.tsx @@ -1,152 +1,396 @@ -import React from 'react'; +import Link from 'next/link'; +import React, { useState } from 'react'; +import { A11y, Navigation, Pagination } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; import type { ICaseStudies } from '@/types/post'; import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; -import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; -import CaseStudyCard from '../../components/CaseStudyCard'; +import Button from '../../components/buttons/Button'; +import Container from '../../components/layout/Container'; import GenericLayout from '../../components/layout/GenericLayout'; import Heading from '../../components/typography/Heading'; import Paragraph from '../../components/typography/Paragraph'; -import TextLink from '../../components/typography/TextLink'; +import AdoptersList from '../../config/adopters.json'; import CaseStudiesList from '../../config/case-studies.json'; -import UsecasesList from '../../config/usecases.json'; interface Resource { - type: string; - url: string; + title: string; + link: string; } interface Adopter { - name: string; - description: string; + companyName: string; + useCase: string; resources: Resource[]; } +const ITEMS_PER_PAGE = 5; + /** - * @description Renders the Case Studies page. + * @description Renders the Case Studies page with modern design and horizontal card carousel. */ -export default function Casestudies() { - const description: string = 'Learn about different case studies based on AsyncAPI spec and related tools.'; - const image: string = '/img/social/case-studies.webp'; - const title: string = 'Case Studies'; +export default function CaseStudies() { + const [currentPage, setCurrentPage] = useState(1); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + const adopters = AdoptersList as Adopter[]; + const totalPages = Math.ceil(adopters.length / ITEMS_PER_PAGE); - const AdoptersList = Object.values(UsecasesList.data) as Adopter[]; + const paginatedAdopters = adopters.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE); return ( - -
-
-
- - {title} + + {/* Hero Section */} +
+ +
+
+ + Case Studies + +
+ + + Real Stories, Real Impact - - The best way to learn how to use AsyncAPI is not only through documentation that usually is focused on - recommendations and best practices. It is also good to confront with real-life case studies that explain - how people really use AsyncAPI and what are their flows. - - - Feel free to submit your case study. We have a template for you. For more details - - read our FAQ - - . + + + Discover how leading companies use AsyncAPI to transform their API architecture, streamline development, + and accelerate innovation. -
-
-
- -
- -
-
-
- - Adopters - - - Feel free to{' '} - - submit a pull request - {' '} - with information about how your company uses AsyncAPI. We know that writing an official case study might - be time consuming and requires too much internal paper work. Let's make sure we can at least - capture a use case that is already a great learning information for the community. - - - {UsecasesList.description} - + +
+
-
- - {/* Desktop Table View */} -
- - - - - - - - - - {AdoptersList.map((entry: Adopter, index: number) => ( - - - - - + + + + {/* Featured Case Studies Cards - Horizontal Carousel */} +
+ +
+ + {(CaseStudiesList as ICaseStudies).map((study, index) => ( + + +
+ {/* Company Logo */} +
+ {study.company.name} +
+ + {/* Description */} + + {study.company.description} + + + {/* CTA Button */} +
+ +
))} -
-
Company nameUse CaseResources
{entry.name}{entry.description} -
    - {entry.resources.map((resource: Resource, resourceIndex: number) => ( -
  • - - {resource.type} - -
  • - ))} -
-
-
- - {/* Mobile Card View */} -
- {AdoptersList.map((entry: Adopter, index: number) => ( -
+ + {/* Custom Navigation Buttons - Only show on mobile/tablet when needed */} + + +
+ + + + {/* Trusted by Industry Leaders - Table Layout */} +
+ +
+ + Trusted by Industry Leaders + + + See how different companies leverage AsyncAPI to solve complex integration challenges and drive digital + transformation. + +
+ + {/* Table Header - Hidden on mobile, visible on tablet+ */} +
+
+
+ + Company + +
+
+ + Use Case + +
+
+ + Resources +
-
-

Use Case:

-

{entry.description}

+
+
+ + {/* Table Rows */} +
+ {paginatedAdopters.map((adopter, index) => ( +
+
+ {/* Company Name */} +
+ + {adopter.companyName} + +
+ + {/* Use Case */} +
+ {adopter.useCase} +
+ + {/* Resources Button */} +
+ +
+
+
+ ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ {/* Page Navigation Buttons */} +
+ + + {Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { + let pageNum; + + if (totalPages <= 5) { + pageNum = i + 1; + } else if (currentPage <= 3) { + pageNum = i + 1; + } else if (currentPage >= totalPages - 2) { + pageNum = totalPages - 4 + i; + } else { + pageNum = currentPage - 2 + i; + } + + return ( + + ); + })} + + {totalPages > 5 && ...} + +
-
-

Resources:

-
    - {entry.resources.map((resource: Resource, resourceIndex: number) => ( -
  • - - {resource.type} - -
  • - ))} -
+ + {/* Go to Page Dropdown - Custom dropdown that opens downward */} +
+ Go to page +
+ + + {/* Dropdown Menu - Opens downward */} + {isDropdownOpen && ( +
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( + + ))} +
+ )} +
- ))} -
-
+ )} +
+
+ + {/* CTA Section with Gradient */} +
+ +
+ + Ready to Share Your Success Story? + + + + Join the growing community of companies transforming their API architecture with AsyncAPI. + + +
+
+
+
+
); } diff --git a/styles/globals.css b/styles/globals.css index 793a55872a7a..434631946040 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -357,4 +357,103 @@ select { border-radius: 16px; } +/* Line clamp utilities for text truncation */ +.line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.line-clamp-4 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; +} +.line-clamp-6 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 6; +} + +/* Smooth gradient animation for CTA section */ +@keyframes gradient-shift { + 0%, 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} + +.animate-gradient { + background-size: 200% 200%; + animation: gradient-shift 15s ease infinite; +} + +/* Custom Swiper Styles for Featured Case Studies */ +.featured-case-studies-swiper { + padding: 20px 0 60px 0; +} + +.featured-case-studies-swiper .swiper-pagination { + bottom: 20px; +} + +.featured-case-studies-swiper .swiper-pagination-bullet { + background: #8851FB; + opacity: 0.5; + width: 10px; + height: 10px; +} + +.featured-case-studies-swiper .swiper-pagination-bullet-active { + opacity: 1; + width: 24px; + border-radius: 5px; +} + +.featured-case-studies-swiper .swiper-slide { + height: auto; + display: flex; +} + +/* Dark mode pagination bullets */ +.dark .featured-case-studies-swiper .swiper-pagination-bullet { + background: #A87EFC; +} + +/* Custom Swiper Styles for Use Cases Carousel */ +.case-studies-swiper { + padding: 20px 0 60px 0; +} + +.case-studies-swiper .swiper-pagination { + bottom: 20px; +} + +.case-studies-swiper .swiper-pagination-bullet { + background: #8851FB; + opacity: 0.5; + width: 10px; + height: 10px; +} + +.case-studies-swiper .swiper-pagination-bullet-active { + opacity: 1; + width: 24px; + border-radius: 5px; +} + +.case-studies-swiper .swiper-slide { + height: auto; + display: flex; +} + +/* Dark mode pagination bullets */ +.dark .case-studies-swiper .swiper-pagination-bullet { + background: #A87EFC; +} From 2409193d75fb6837870347b2d2331fede797abd1 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Sat, 25 Oct 2025 23:14:28 +0530 Subject: [PATCH 04/41] fix: blog desktop design --- components/form/Select.tsx | 4 +- components/layout/GenericLayout.tsx | 2 +- components/navigation/BlogPostItem.tsx | 91 +++++---- pages/blog/index.tsx | 193 ++++++++++++++++---- types/components/navigation/BlogPostType.ts | 5 +- 5 files changed, 210 insertions(+), 85 deletions(-) diff --git a/components/form/Select.tsx b/components/form/Select.tsx index 12d150fe6823..b4a60beac9c5 100644 --- a/components/form/Select.tsx +++ b/components/form/Select.tsx @@ -31,12 +31,12 @@ export default function Select({ className = '', onChange = () => {}, options, s data-testid='Select-form' onChange={handleOnChange} className={twMerge( - `form-select h-full pl-2 pr-8 inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500 ${className}` + `form-select h-full pl-2 pr-8 inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-700 shadow-sm px-4 py-2 bg-white dark:bg-dark-card text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-dark-background focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 dark:focus:ring-offset-dark-background focus:ring-primary-500 ${className}` )} value={selected} > {options.map((option, index) => ( - ))} diff --git a/components/layout/GenericLayout.tsx b/components/layout/GenericLayout.tsx index 9c10d09996aa..1764bc9122fc 100644 --- a/components/layout/GenericLayout.tsx +++ b/components/layout/GenericLayout.tsx @@ -36,7 +36,7 @@ export default function GenericLayout({ } return ( -
+
diff --git a/components/navigation/BlogPostItem.tsx b/components/navigation/BlogPostItem.tsx index 2b40723e626a..d7f61eb4a997 100644 --- a/components/navigation/BlogPostItem.tsx +++ b/components/navigation/BlogPostItem.tsx @@ -51,6 +51,15 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref: case BlogPostType.Communication: typeColors = ['bg-teal-100', 'text-teal-800']; break; + case BlogPostType.Conference: + typeColors = ['bg-purple-100', 'text-purple-800']; + break; + case BlogPostType.Engineering: + typeColors = ['bg-blue-100', 'text-blue-800']; + break; + case BlogPostType.Community: + typeColors = ['bg-indigo-100', 'text-indigo-800']; + break; default: } @@ -59,9 +68,7 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref:
{post.featured && ( @@ -74,64 +81,50 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref: -
+
- + {post.type} - - - {post.title} - - - - - + + {post.title} + + + +
-
-
- +
+
+
+ +
+
+ + + {post.authors + .map((author) => author.name) + .join(', ') + .split(', ') + .slice(0, 2) + .join(' & ')} + + + + Data + +
-
- - - {post.authors - .map((author, index) => - author.link ? ( - - ) : ( - author.name - ) - ) - .reduce((prev, curr, index) => ( - - {prev} & {curr} - - ))} - - - - - · - {post.readingTime} min read +
+ + {post.readingTime} mins read
diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index b6a13e507157..2325afcf025d 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -35,8 +35,17 @@ export default function BlogIndexPage() { : [] ); const [isClient, setIsClient] = useState(false); + const [activeTab, setActiveTab] = useState('All Posts'); + const [currentPage, setCurrentPage] = useState(1); + const postsPerPage = 9; - const onFilter = (data: IBlogPost[]) => setPosts(data); + const onFilter = (data: IBlogPost[]) => { + setPosts(data); + // Reset tab filter to "All Posts" when dropdown filters are applied + if (Object.keys(router.query).length > 0) { + setActiveTab('All Posts'); + } + }; const toFilter = [ { name: 'type' @@ -53,6 +62,7 @@ export default function BlogIndexPage() { router.push(`${router.pathname}`, undefined, { shallow: true }); + setActiveTab('All Posts'); }; const showClearFilters = Object.keys(router.query).length > 0; @@ -63,40 +73,56 @@ export default function BlogIndexPage() { setIsClient(true); }, []); + // Filter posts by active tab (only if no dropdown filters are active) + const hasDropdownFilters = Object.keys(router.query).length > 0; + const filteredByTab = posts.filter((post) => { + // If dropdown filters are active, show all posts (dropdown takes priority) + if (hasDropdownFilters) return true; + // Otherwise, apply tab filter + if (activeTab === 'All Posts') return true; + return post.type.toLowerCase() === activeTab.toLowerCase(); + }); + + // Calculate pagination + const totalPages = Math.ceil(filteredByTab.length / postsPerPage); + const indexOfLastPost = currentPage * postsPerPage; + const indexOfFirstPost = indexOfLastPost - postsPerPage; + const currentPosts = filteredByTab.slice(indexOfFirstPost, indexOfLastPost); + + // Reset to page 1 when changing tabs or filters + useEffect(() => { + setCurrentPage(1); + }, [activeTab, posts]); + + // Reset tab to "All Posts" when dropdown filters change + useEffect(() => { + if (hasDropdownFilters) { + setActiveTab('All Posts'); + } + }, [router.query]); + + const tabs = ['All Posts', 'Community', 'Conference', 'Communication', 'Engineering', 'Strategy']; + return (
-
-
-
- + Welcome to our blog! - + Find the latest and greatest stories from our community - - Want to publish a blog post? We love community stories.{' '} - - Submit yours! + + Stay in touch! Get blog posts delivered directly to your{' '} + + email - - - We have an - RSS feed - RSS Feed, too! + .
-
+
Clear filters )}
+ + {/* Category Tabs */} +
+
+ {tabs.map((tab) => ( + + ))} +
+
+
- {Object.keys(posts).length === 0 && ( + {currentPosts.length === 0 && (
-

No post matches your filter

+

+ No post matches your filter +

)} - {Object.keys(posts).length > 0 && isClient && ( -
    - {posts.map((post, index) => ( - - ))} -
+ {currentPosts.length > 0 && isClient && ( + <> +
    + {currentPosts.map((post, index) => ( + + ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + + {Array.from({ length: totalPages }, (_, i) => i + 1) + .filter((page) => { + return ( + page === 1 || + page === totalPages || + (page >= currentPage - 1 && page <= currentPage + 1) + ); + }) + .map((page, index, array) => { + if (index > 0 && page - array[index - 1] > 1) { + return ( + + ... + + + ); + } + + return ( + + ); + })} + + + + + Go to page + + +
+ )} + )} {Object.keys(posts).length > 0 && !isClient && (
diff --git a/types/components/navigation/BlogPostType.ts b/types/components/navigation/BlogPostType.ts index b56b89402cec..10cff9b76b60 100644 --- a/types/components/navigation/BlogPostType.ts +++ b/types/components/navigation/BlogPostType.ts @@ -2,5 +2,8 @@ export enum BlogPostType { Video = 'video', Marketing = 'marketing', Strategy = 'strategy', - Communication = 'communication' + Communication = 'communication', + Conference = 'conference', + Engineering = 'engineering', + Community = 'community' } From d2fbef113e884b7e9deae2ad58984c4155f66bfa Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Sat, 25 Oct 2025 23:26:41 +0530 Subject: [PATCH 05/41] fix: fixing mobile view --- pages/blog/index.tsx | 261 +++++++++++++++++++++++++------------------ 1 file changed, 154 insertions(+), 107 deletions(-) diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index 2325afcf025d..f56371849d05 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -37,7 +37,7 @@ export default function BlogIndexPage() { const [isClient, setIsClient] = useState(false); const [activeTab, setActiveTab] = useState('All Posts'); const [currentPage, setCurrentPage] = useState(1); - const postsPerPage = 9; + const [postsPerPage, setPostsPerPage] = useState(9); const onFilter = (data: IBlogPost[]) => { setPosts(data); @@ -71,6 +71,24 @@ export default function BlogIndexPage() { useEffect(() => { setIsClient(true); + + // Set posts per page based on screen size + const handleResize = () => { + if (window.innerWidth < 640) { + setPostsPerPage(5); // Mobile: 5 posts + } else { + setPostsPerPage(9); // Tablet/Desktop: 9 posts + } + }; + + // Set initial value + handleResize(); + + // Add event listener + window.addEventListener('resize', handleResize); + + // Cleanup + return () => window.removeEventListener('resize', handleResize); }, []); // Filter posts by active tab (only if no dropdown filters are active) @@ -89,10 +107,10 @@ export default function BlogIndexPage() { const indexOfFirstPost = indexOfLastPost - postsPerPage; const currentPosts = filteredByTab.slice(indexOfFirstPost, indexOfLastPost); - // Reset to page 1 when changing tabs or filters + // Reset to page 1 when changing tabs, filters, or posts per page useEffect(() => { setCurrentPage(1); - }, [activeTab, posts]); + }, [activeTab, posts, postsPerPage]); // Reset tab to "All Posts" when dropdown filters change useEffect(() => { @@ -107,14 +125,14 @@ export default function BlogIndexPage() {
-
- +
+ Welcome to our blog! - + Find the latest and greatest stories from our community - + Stay in touch! Get blog posts delivered directly to your{' '} email @@ -122,41 +140,46 @@ export default function BlogIndexPage() { .
-
- - {showClearFilters && ( - - )} -
- - {/* Category Tabs */} -
-
- {tabs.map((tab) => ( + {/* Filters Section */} +
+
+ + {showClearFilters && ( - ))} + )} +
+
+ + {/* Category Tabs - Hidden on mobile */} +
+
+
+ {tabs.map((tab) => ( + + ))} +
@@ -171,7 +194,7 @@ export default function BlogIndexPage() { )} {currentPosts.length > 0 && isClient && ( <> -
    +
      {currentPosts.map((post, index) => ( ))} @@ -179,79 +202,103 @@ export default function BlogIndexPage() { {/* Pagination */} {totalPages > 1 && ( -
      - +
      + {/* Mobile Pagination */} +
      + + + Page {currentPage} of {totalPages} + + +
      + + {/* Desktop/Tablet Pagination */} +
      + - {Array.from({ length: totalPages }, (_, i) => i + 1) - .filter((page) => { - return ( - page === 1 || - page === totalPages || - (page >= currentPage - 1 && page <= currentPage + 1) - ); - }) - .map((page, index, array) => { - if (index > 0 && page - array[index - 1] > 1) { + {Array.from({ length: totalPages }, (_, i) => i + 1) + .filter((page) => { return ( - - ... - - + page === 1 || + page === totalPages || + (page >= currentPage - 1 && page <= currentPage + 1) ); - } + }) + .map((page, index, array) => { + if (index > 0 && page - array[index - 1] > 1) { + return ( + + ... + + + ); + } - return ( - - ); - })} + return ( + + ); + })} - + - - Go to page - - + + Go to page + + +
      )} From 088091412677871f99669c3a63c82c95968cec90 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Sat, 25 Oct 2025 23:32:34 +0530 Subject: [PATCH 06/41] fix: changing heading sizes --- pages/blog/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index f56371849d05..37185eb52231 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -126,7 +126,7 @@ export default function BlogIndexPage() {
      - + Welcome to our blog! From d1072e19eff08448ec333ec484f17eeb96199a6c Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Mon, 3 Nov 2025 11:30:41 +0530 Subject: [PATCH 07/41] fix: update ambassadors page --- components/icons/Book.tsx | 21 ++ components/icons/Clipboard.tsx | 15 +- components/icons/CodeBrackets.tsx | 14 + components/icons/Document.tsx | 14 + components/icons/LightBulb.tsx | 21 +- components/icons/Microphone.tsx | 14 + components/icons/Play.tsx | 10 + components/icons/Star.tsx | 10 + components/icons/Terminal.tsx | 14 + components/icons/UsersGroup.tsx | 10 + components/icons/Video.tsx | 10 + pages/community/ambassadors/index.tsx | 466 ++++++++++++++++++-------- 12 files changed, 453 insertions(+), 166 deletions(-) create mode 100644 components/icons/Book.tsx create mode 100644 components/icons/CodeBrackets.tsx create mode 100644 components/icons/Document.tsx create mode 100644 components/icons/Microphone.tsx create mode 100644 components/icons/Play.tsx create mode 100644 components/icons/Star.tsx create mode 100644 components/icons/Terminal.tsx create mode 100644 components/icons/UsersGroup.tsx create mode 100644 components/icons/Video.tsx diff --git a/components/icons/Book.tsx b/components/icons/Book.tsx new file mode 100644 index 000000000000..05f83c3b448b --- /dev/null +++ b/components/icons/Book.tsx @@ -0,0 +1,21 @@ +export default function IconBook({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Clipboard.tsx b/components/icons/Clipboard.tsx index c0add09acf31..84f32909d98c 100644 --- a/components/icons/Clipboard.tsx +++ b/components/icons/Clipboard.tsx @@ -1,13 +1,12 @@ -import React from 'react'; - -/* eslint-disable max-len */ -/** - * @description Icons for asyncapi website - */ export default function IconClipboard({ className = '' }) { return ( - - + + + ); } diff --git a/components/icons/CodeBrackets.tsx b/components/icons/CodeBrackets.tsx new file mode 100644 index 000000000000..13bd49c2f3a3 --- /dev/null +++ b/components/icons/CodeBrackets.tsx @@ -0,0 +1,14 @@ +export default function IconCodeBrackets({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Document.tsx b/components/icons/Document.tsx new file mode 100644 index 000000000000..595f7894240f --- /dev/null +++ b/components/icons/Document.tsx @@ -0,0 +1,14 @@ +export default function IconDocument({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/LightBulb.tsx b/components/icons/LightBulb.tsx index 884e8b618d4a..97fe3f25ecde 100644 --- a/components/icons/LightBulb.tsx +++ b/components/icons/LightBulb.tsx @@ -1,20 +1,9 @@ -import React from 'react'; - -/* eslint-disable max-len */ -/** - * @description Icons for asyncapi website - */ -export default function IconLightBulb({ className = '' }) { +export default function IconLightbulb({ className = '' }) { return ( - - + + ); } + + diff --git a/components/icons/Microphone.tsx b/components/icons/Microphone.tsx new file mode 100644 index 000000000000..f3756ca7ba3c --- /dev/null +++ b/components/icons/Microphone.tsx @@ -0,0 +1,14 @@ +export default function IconMicrophone({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Play.tsx b/components/icons/Play.tsx new file mode 100644 index 000000000000..7551c192a1db --- /dev/null +++ b/components/icons/Play.tsx @@ -0,0 +1,10 @@ +export default function IconPlay({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Star.tsx b/components/icons/Star.tsx new file mode 100644 index 000000000000..0b701fdbc0a1 --- /dev/null +++ b/components/icons/Star.tsx @@ -0,0 +1,10 @@ +export default function IconStar({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Terminal.tsx b/components/icons/Terminal.tsx new file mode 100644 index 000000000000..eb8a4c6da366 --- /dev/null +++ b/components/icons/Terminal.tsx @@ -0,0 +1,14 @@ +export default function IconTerminal({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/UsersGroup.tsx b/components/icons/UsersGroup.tsx new file mode 100644 index 000000000000..73a04aa9056d --- /dev/null +++ b/components/icons/UsersGroup.tsx @@ -0,0 +1,10 @@ +export default function IconUsersGroup({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/components/icons/Video.tsx b/components/icons/Video.tsx new file mode 100644 index 000000000000..564d0b59bc18 --- /dev/null +++ b/components/icons/Video.tsx @@ -0,0 +1,10 @@ +export default function IconVideo({ className = '' }) { + return ( + + + + ); +} + + + diff --git a/pages/community/ambassadors/index.tsx b/pages/community/ambassadors/index.tsx index 636277ed34b0..1e3a49082cc9 100644 --- a/pages/community/ambassadors/index.tsx +++ b/pages/community/ambassadors/index.tsx @@ -1,11 +1,19 @@ import { sortBy } from 'lodash'; import Link from 'next/link'; -import React from 'react'; +import React, { useState } from 'react'; import type { Ambassador } from '@/types/pages/community/Community'; -import { HeadingTypeStyle } from '@/types/typography/Heading'; +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; import Button from '../../../components/buttons/Button'; +import IconClipboard from '../../../components/icons/Clipboard'; +import IconDocument from '../../../components/icons/Document'; +import IconLightbulb from '../../../components/icons/LightBulb'; +import IconMicrophone from '../../../components/icons/Microphone'; +import IconPlay from '../../../components/icons/Play'; +import IconStar from '../../../components/icons/Star'; +import IconTerminal from '../../../components/icons/Terminal'; +import IconVideo from '../../../components/icons/Video'; import GenericLayout from '../../../components/layout/GenericLayout'; import NewsletterSubscribe from '../../../components/NewsletterSubscribe'; import Heading from '../../../components/typography/Heading'; @@ -39,6 +47,25 @@ export function addAdditionalUserInfo(user: Ambassador) { return userData; } +/** + * @description Get the appropriate icon component based on the contribution title + * @param {string} title - The contribution title + */ +function getContributionIcon(title: string) { + const iconMap: Record> = { + 'Written content': IconDocument, + 'Video content': IconVideo, + 'Live streams': IconPlay, + 'Give talks': IconMicrophone, + 'Interactive Learning': IconLightbulb, + 'Build real-life usecases example': IconTerminal, + 'AsyncAPI Contributions': IconStar, + 'Gather Use-Cases': IconClipboard + }; + + return iconMap[title] || IconDocument; +} + /** * @description The main page for the AsyncAPI Ambassador Program. */ @@ -49,176 +76,331 @@ export default function Index() { ['name'] ); + const [currentPage, setCurrentPage] = useState(1); + const postsPerPage = 6; + const indexOfLastPost = currentPage * postsPerPage; + const indexOfFirstPost = indexOfLastPost - postsPerPage; + const currentAmbassadors = asyncapiAmbassadors.slice(indexOfFirstPost, indexOfLastPost); + const totalPages = Math.ceil(asyncapiAmbassadors.length / postsPerPage); + return ( -
      -
      -

      - Teachers. Champions. Ambassadors! -

      - - Passionate about event-driven architectures or message-driven APIs? Become an AsyncAPI Ambassador and help - the OSS community build the future of APIs. - -
      -
      -
      - ambassador-cover -
      -
      -
      - + {/* Video Section */} +
      +
      +
      +
      + +
      +
      +
      +

      Watch our Ambassador Program overview

      +
      +
      +
      -
      -
      - AsyncAPI Ambassador Contributions - - AsyncAPI Ambassadors are passionate about APIs and AsyncAPI. They share their interest, expertise, and - excitement within their communities to help others build better software. +
      + + {/* Contributions Section */} +
      +
      +
      + + AsyncAPI Ambassador Contributions + + + Our ambassadors are passionate about APIs and AsyncAPI. They share their knowledge, expertise, and + excitement within their communities to help others build the future of APIs.
      + +
      + {ambassadorList.contents.map((link, index) => { + const IconComponent = getContributionIcon(link.title); + + return ( +
      +
      +
      + +
      +
      + + {link.title} + +

      {link.details}

      +
      + ); + })} +
      -
        - {ambassadorList.contents.map((link) => ( -
      • - {link.title} - - {link.title} - -

        {link.details}

        -
      • - ))} -
      -
      - Join these AsyncAPI Ambassadors - - Learn and share knowledge with community members - -
      - {asyncapiAmbassadors.map((ambassador: Ambassador, index: number) => ( -
      +
      +
      + + Meet Our AsyncAPI Ambassadors + + -
      -
      -
      {ambassador.name}
      -
      {ambassador.country}
      -
      + Learn and grow alongside community members who are passionate about AsyncAPI and event-driven + architectures. + +
      + +
      + {currentAmbassadors.map((ambassador: Ambassador, index: number) => ( +
      -
      -
      - {ambassador.name} +
      + {ambassador.country && ( +
      + {ambassador.country} +
      + )} +
      + {ambassador.name}
      -
      {ambassador.title}
      -
      -
      -
      {ambassador.bio}
      -
      - - Twitter ↗ - - - Github ↗ - - - Linkedin ↗ - + +
      +
      +
      +

      {ambassador.name}

      +

      {ambassador.title}

      +
      +
      + +

      {ambassador.company}

      + +
      +

      Socials

      +
      + {ambassador.twitterUrl && ( + + + + + + )} + {ambassador.githubUrl && ( + + + + + + )} + {ambassador.linkedinUrl && ( + + + + + + )} +
      +
      + ))} +
      + + {/* Pagination */} + {totalPages > 1 && ( +
      + + + {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( + + ))} + +
      - ))} + )}
      -
      -
      -
      - Tokens of our appreciation - + {/* Tokens Section */} +
      +
      +
      + + Tokens of our appreciation + + We appreciate your commitment and passion for sharing your knowledge with your communities. Let us support you!
      -
      -
      -
        - {ambassadorList.tokens.map((token) => ( -
      • -
        -
        - {token.emoji} + +
        + {ambassadorList.tokens.map((token, index) => ( +
        +
        +
        + {token.emoji}
        -
        - {token.title} -

        {token.details}

        -
        -
      • + + {token.title} + +

        {token.details}

        +
      ))} -
    +
-
-
-
- Become an AsyncAPI Ambassador - - The AsyncAPI Ambassador program is now open for applications! If you're selected, you'll join - AsyncAPI's mission of helping community members all over the world, build the future of - Event-Driven APIs. - -
-
-
+
+ + {/* CTA Section */} +
+ {/* Gradient background div */} +
+
+ + Become an AsyncAPI Ambassador + + + The AsyncAPI Ambassador program is now open for applications! If you're selected, you'll join AsyncAPI's + mission of helping community members all over the world, build the future of Event-Driven APIs. + +
+
+
From 92ab9d0ae1f746888afb32d158fced42ca0019d8 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Tue, 4 Nov 2025 19:54:09 +0530 Subject: [PATCH 08/41] feat:adding graphic on ambassadors page Signed-off-by: Shriya-Chauhan --- pages/community/ambassadors/index.tsx | 121 +++++++++++++++++++------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/pages/community/ambassadors/index.tsx b/pages/community/ambassadors/index.tsx index 1e3a49082cc9..35622148f7a1 100644 --- a/pages/community/ambassadors/index.tsx +++ b/pages/community/ambassadors/index.tsx @@ -6,13 +6,16 @@ import type { Ambassador } from '@/types/pages/community/Community'; import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; import Button from '../../../components/buttons/Button'; +import IconBook from '../../../components/icons/Book'; import IconClipboard from '../../../components/icons/Clipboard'; +import IconCode from '../../../components/icons/Code'; import IconDocument from '../../../components/icons/Document'; import IconLightbulb from '../../../components/icons/LightBulb'; import IconMicrophone from '../../../components/icons/Microphone'; import IconPlay from '../../../components/icons/Play'; import IconStar from '../../../components/icons/Star'; import IconTerminal from '../../../components/icons/Terminal'; +import IconUsersGroup from '../../../components/icons/UsersGroup'; import IconVideo from '../../../components/icons/Video'; import GenericLayout from '../../../components/layout/GenericLayout'; import NewsletterSubscribe from '../../../components/NewsletterSubscribe'; @@ -88,33 +91,85 @@ export default function Index() { {/* Hero Section */}
-
- - Community - -

- Teachers.
- Champions.
- Ambassadors. -

- - Passionate about AsyncAPI? Join our global community of ambassadors who drive innovation, education, and - adoption of event-driven architectures. - -
-
+
+ + {/* Right Column - Feature Cards */} +
+
+ {/* Educational Content Card - Purple/Pink */} +
+
+ +
+

Educational Content

+

Creating tutorials and guides

+
+ + {/* Video Tutorials Card - Blue/Cyan */} +
+
+ +
+

Video Tutorials

+

Sharing knowledge through video

+
+ + {/* Community Building Card - Green/Emerald */} +
+
+ +
+

Community Building

+

Growing local communities

+
+ + {/* Open Source Card - Orange/Red */} +
+
+ +
+

Open Source

+

Contributing to projects

+
+
@@ -240,10 +295,10 @@ export default function Index() { href={ambassador.twitterUrl} target='_blank' rel='noreferrer' - className='text-gray-600 dark:text-gray-400 hover:text-primary-500 transition-colors' + className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors duration-200' data-testid='Ambassadors-members-twitter' > - + @@ -253,9 +308,9 @@ export default function Index() { href={ambassador.githubUrl} target='_blank' rel='noreferrer' - className='text-gray-600 dark:text-gray-400 hover:text-primary-500 transition-colors' + className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors duration-200' > - + - + From b629cdf2d2ed15c1adb41fcfa2f8188ba3192bdd Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Tue, 4 Nov 2025 21:04:59 +0530 Subject: [PATCH 09/41] fix: tsc page draft Signed-off-by: Shriya-Chauhan --- components/community/TSCMemberCard.tsx | 101 ++++++ pages/community/tsc.tsx | 447 ++++++++++++++++++++++--- types/pages/community/Community.ts | 4 + 3 files changed, 501 insertions(+), 51 deletions(-) create mode 100644 components/community/TSCMemberCard.tsx diff --git a/components/community/TSCMemberCard.tsx b/components/community/TSCMemberCard.tsx new file mode 100644 index 000000000000..0022023c2f5c --- /dev/null +++ b/components/community/TSCMemberCard.tsx @@ -0,0 +1,101 @@ +import type { Tsc } from '@/types/pages/community/Community'; + +interface TSCMemberCardProps { + member: Tsc; +} + +/** + * @description Component for displaying a TSC member card + * @param {TSCMemberCardProps} props - The props for TSC member card + * @param {Tsc} props.member - The TSC member data + */ +export default function TSCMemberCard({ member }: TSCMemberCardProps) { + return ( +
+
+ {member.name} +
+

+ {member.name} +

+

+ {member.availableForHire ? 'Available for hire' : member.company || 'Individual Member'} +

+
+ {member.availableForHire && ( + + Available + + )} +
+ + {member.reposList && member.reposList.length > 0 && ( +
+

Maintainer of:

+
+ {member.reposList.slice(0, 2).map((repo: { name: string; url: string }) => ( + + {repo.name} + + ))} + {member.reposList.length > 2 && ( + + +{member.reposList.length - 2} + + )} +
+
+ )} + +
+ {member.githubUrl && ( + + + + + + )} + {member.twitterUrl && ( + + + + + + )} + {member.linkedinUrl && ( + + + + + + )} +
+
+ ); +} + diff --git a/pages/community/tsc.tsx b/pages/community/tsc.tsx index d7f1c4438ce4..5478ce1ea52e 100644 --- a/pages/community/tsc.tsx +++ b/pages/community/tsc.tsx @@ -1,65 +1,410 @@ -import CommunityLayout, { Membership } from '@/components/layout/CommunityLayout'; -import TextLink from '@/components/typography/TextLink'; +import { sortBy } from 'lodash'; +import { useState } from 'react'; + +import type { Tsc } from '@/types/pages/community/Community'; + +import tscBoardList from '../../config/TSC_BOARD_MEMBERS.json'; +import Button from '../../components/buttons/Button'; +import TSCMemberCard from '../../components/community/TSCMemberCard'; +import IconDocument from '../../components/icons/Document'; +import IconUsersGroup from '../../components/icons/UsersGroup'; +import IconArrowRight from '../../components/icons/ArrowRight'; +import GenericLayout from '../../components/layout/GenericLayout'; +import NewsletterSubscribe from '../../components/NewsletterSubscribe'; + +/** + * @description Add additional user information to the user object having TSC data + * @param {any} user - The user object having TSC data + */ +function addAdditionalUserInfo(user: any): Tsc { + const userData: any = { + ...user + }; + + // if username is not present, use the github username + if (!userData.name) { + userData.name = userData.github; + } + + // add social links + if (userData.github) { + userData.githubUrl = `https://www.github.com/${userData.github}`; + } + if (userData.linkedin) { + userData.linkedinUrl = `https://www.linkedin.com/in/${userData.linkedin}`; + } + if (userData.twitter) { + userData.twitterUrl = `https://www.twitter.com/${userData.twitter}`; + } + + // add avatar url + userData.avatarUrl = `${userData.githubUrl}.png`; + + // make repo links + if (userData.repos) { + userData.reposList = userData.repos.map((repoName: string) => ({ + name: repoName, + url: `https://www.github.com/asyncapi/${repoName}` + })); + } + + return userData; +} /** * @description This function returns the TSC component. */ export default function TSC() { + const image = '/img/social/community-tsc.webp'; + const tscMembers = sortBy( + tscBoardList.map((user) => addAdditionalUserInfo(user)), + ['name'] + ).filter((user) => user.isTscMember); + + const [currentPage, setCurrentPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(''); + const [filterType, setFilterType] = useState<'all' | 'maintainer' | 'available' | 'company'>('all'); + const membersPerPage = 9; + + // Filter members + const filteredMembers = tscMembers.filter((member) => { + const matchesSearch = member.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (member.github && member.github.toLowerCase().includes(searchTerm.toLowerCase())); + + const matchesFilter = + filterType === 'all' || + (filterType === 'maintainer' && member.repos && member.repos.length > 0) || + (filterType === 'available' && member.availableForHire) || + (filterType === 'company' && member.company); + + return matchesSearch && matchesFilter; + }); + + const totalPages = Math.ceil(filteredMembers.length / membersPerPage); + const indexOfLastMember = currentPage * membersPerPage; + const indexOfFirstMember = indexOfLastMember - membersPerPage; + const currentMembers = filteredMembers.slice(indexOfFirstMember, indexOfLastMember); + return ( - -
-
-

What is a TSC?

-

- The Technical Steering Committee (TSC) is responsible for the oversight of the AsyncAPI Initiative. - Maintainers (aka committers) make decisions at the given repository/project level. The TSC helps to make - decisions on a higher level, or when maintainers cannot find a consensus. -

+ + {/* Hero Section */} +
+
+
+

+ Technical Steering Committee +

+

+ Meet the dedicated maintainers and contributors who guide the AsyncAPI Initiative forward, making strategic decisions that shape the future of event-driven architecture. +

+
+
+
+
+
+ + {/* Stats Section */} +
+
+
+
+
+
+ +
+
10+
+
Active members
+
+
+
+ +
+
100%
+
Open Source
+
+
+
+
+
+ + {/* Info Cards Section */} +
+
+
+ {/* What is TSC Card */} +
+
+ +
+

What is TSC?

+

+ The Technical Steering Committee (TSC) is responsible for the oversight of the AsyncAPI Initiative. Maintainers take committee seats decisions on the direction of the project, including releases, contribution policies, and other strategic matters. +

+
+ + {/* How to Become a Member Card */} +
+
+ +
+

How to become a member?

+

+ Anybody can become a maintainer of the TSC. All you have to do is become a maintainer of one of the AsyncAPI projects. To become a maintainer, you need to be nominated by a TSC member and then other maintainers will make a vote. +

+
+ + {/* Our Governance Model Card */} +
+
+ +
+

Our governance model

+

+ AsyncAPI Initiative runs under an Open Governance Model with its technical project and community assets under a neutral home, with an independent board of directors representing a cross-section. +

+
+
+
+
+ + {/* Newsletter Subscription Section */} +
+
+
+ +
-
-

- How can I become a TSC member? -

-

- Anybody can become a member of the TSC. All you have to do is become a maintainer of one of the AsyncAPI - projects! To become a maintainer, you just need to regularly contribute to one of the projects and then - other maintainers will invite you to join. You can also build a great AsyncAPI-based project that we - don't have yet in our GitHub organization and donate it (we'll ask you to stay as a maintainer). - Follow this - - Link - -  to know more! +

+ + {/* Current TSC Members Section */} +
+
+
+

+ Current TSC Members +

+

+ Meet our dedicated technical steering committee +

+
+ + {/* Search and Filters */} +
+
+ setSearchTerm(e.target.value)} + className='w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent' + /> +
+
+ + + + +
+
+ +

+ Showing {filteredMembers.length} of {tscMembers.length} members

+ + {/* Members Grid */} +
+ {currentMembers.map((member) => ( + + ))} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ {/* Previous Button */} + + + {/* Page Numbers */} + {currentPage > 1 && ( + + )} + + {currentPage === 1 && ( + + )} + + {currentPage > 2 && currentPage < totalPages - 1 && ( + <> + {currentPage > 3 && ...} + {currentPage > 2 && ( + + )} + + )} + + {currentPage < totalPages && currentPage > 1 && ( + <> + {currentPage === 2 && ( + + )} + {currentPage > 2 && currentPage < totalPages - 1 && ( + ... + )} + + )} + + {currentPage < totalPages - 1 && totalPages > 2 && ( + ... + )} + + {currentPage !== totalPages && totalPages > 1 && ( + + )} + + {currentPage === totalPages && ( + + )} + + {/* Next Button */} + + + {/* Go to Page Dropdown */} +
+ Go to page + +
+
+ )}
-
-

Our governance model

-

- AsyncAPI Initiative runs under an{' '} - - Open Governance Model - {' '} - that gives power to the people actively involved and working on the project. No matter if you are an - individual contributor or backed by a company, you have equal rights. Read{' '} - - this - {' '} - article to learn more. +

+ + {/* Want to Join TSC CTA */} +
+
+

+ Want to join TSC? +

+

+ Join our community and help shape the future of AsyncAPI

+
- + ); } diff --git a/types/pages/community/Community.ts b/types/pages/community/Community.ts index 846c10293729..ddc525f1cc1b 100644 --- a/types/pages/community/Community.ts +++ b/types/pages/community/Community.ts @@ -50,4 +50,8 @@ export interface Tsc { githubID: number isBoardMember?: boolean; isBoardChair?: boolean; + githubUrl?: string; + twitterUrl?: string; + linkedinUrl?: string; + reposList?: { name: string; url: string }[]; }; From ea75db5588b8064c79c1cc34b2f52b03a170df5a Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Tue, 4 Nov 2025 22:08:57 +0530 Subject: [PATCH 10/41] fix: roadmaps page redesign Signed-off-by: Shriya-Chauhan --- components/icons/Gear.tsx | 9 + components/icons/Lightning.tsx | 8 + components/icons/Plus.tsx | 8 + components/roadmap/GoalCardRoadmapPage.tsx | 26 +++ components/roadmap/RoadmapColumn.tsx | 11 +- components/roadmap/RoadmapItem.tsx | 6 +- components/roadmap/RoadmapPill.tsx | 6 +- pages/roadmap.tsx | 235 ++++++++++----------- 8 files changed, 174 insertions(+), 135 deletions(-) create mode 100644 components/icons/Gear.tsx create mode 100644 components/icons/Lightning.tsx create mode 100644 components/icons/Plus.tsx create mode 100644 components/roadmap/GoalCardRoadmapPage.tsx diff --git a/components/icons/Gear.tsx b/components/icons/Gear.tsx new file mode 100644 index 000000000000..0cc43603d53e --- /dev/null +++ b/components/icons/Gear.tsx @@ -0,0 +1,9 @@ +export default function IconGear({ className = '' }) { + return ( + + + + + ); +} + diff --git a/components/icons/Lightning.tsx b/components/icons/Lightning.tsx new file mode 100644 index 000000000000..02400d7a5a6f --- /dev/null +++ b/components/icons/Lightning.tsx @@ -0,0 +1,8 @@ +export default function IconLightning({ className = '' }) { + return ( + + + + ); +} + diff --git a/components/icons/Plus.tsx b/components/icons/Plus.tsx new file mode 100644 index 000000000000..2d989a9ed1ad --- /dev/null +++ b/components/icons/Plus.tsx @@ -0,0 +1,8 @@ +export default function IconPlus({ className = '' }) { + return ( + + + + ); +} + diff --git a/components/roadmap/GoalCardRoadmapPage.tsx b/components/roadmap/GoalCardRoadmapPage.tsx new file mode 100644 index 000000000000..31491bbf3661 --- /dev/null +++ b/components/roadmap/GoalCardRoadmapPage.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +interface GoalCardRoadmapPageProps { + icon: React.ComponentType<{ className?: string }>; + title: string; + description: string; +} + +/** + * @description GoalCardRoadmapPage component displays a goal with an icon, title, and description. + * Used in the roadmap page to showcase AsyncAPI's goals. + */ +export default function GoalCardRoadmapPage({ icon: Icon, title, description }: GoalCardRoadmapPageProps) { + return ( +
+
+
+ +
+
+

{title}

+

{description}

+
+ ); +} + diff --git a/components/roadmap/RoadmapColumn.tsx b/components/roadmap/RoadmapColumn.tsx index a8fffb5fb2cf..741ae6ebec06 100644 --- a/components/roadmap/RoadmapColumn.tsx +++ b/components/roadmap/RoadmapColumn.tsx @@ -13,6 +13,7 @@ interface IRoadmapColumnProps { colorClass: string; items?: any[]; childrenCollapsed?: boolean; + className?: string; } /** @@ -22,21 +23,23 @@ interface IRoadmapColumnProps { * @param {string} props.colorClass - The color class for styling. * @param {array} props.items - The array of items. * @param {boolean} props.childrenCollapsed - Whether children items are collapsed. + * @param {string} props.className - Optional className for custom styling. */ export default function RoadmapColumn({ title, description, colorClass, items = [], - childrenCollapsed = false + childrenCollapsed = false, + className = '' }: IRoadmapColumnProps): React.ReactElement { return (
-
- +
+ {title} - + {description}
diff --git a/components/roadmap/RoadmapItem.tsx b/components/roadmap/RoadmapItem.tsx index 34787b7252e6..903635c5f195 100644 --- a/components/roadmap/RoadmapItem.tsx +++ b/components/roadmap/RoadmapItem.tsx @@ -34,7 +34,7 @@ export default function RoadmapItem({ item, colorClass, showConnector = true, co const [isCollapsed, setIsCollapsed] = useState(collapsed); const isCollapsible = item.solutions !== undefined; - const connectorClasses = 'border-l-2 border-dashed border-gray-300'; + const connectorClasses = 'border-l-2 border-dashed border-gray-300 dark:border-gray-600'; const classNames = `pt-2 ${showConnector && connectorClasses}`; return ( @@ -42,7 +42,7 @@ export default function RoadmapItem({ item, colorClass, showConnector = true, co
{showConnector && (
-
+
)} + )} ); diff --git a/components/roadmap/RoadmapPill.tsx b/components/roadmap/RoadmapPill.tsx index d73ac46e84bf..d2276fe2f98f 100644 --- a/components/roadmap/RoadmapPill.tsx +++ b/components/roadmap/RoadmapPill.tsx @@ -14,7 +14,7 @@ function DoneIcon() { viewBox='0 0 24 24' strokeWidth={1.5} stroke='currentColor' - className='-mt-0.5 mr-2 inline-block size-6 text-green-600' + className='-mt-0.5 mr-2 inline-block size-6 text-green-600 dark:text-green-400' >
@@ -76,7 +76,7 @@ export default function Pill({ href={item.url} rel='noopener noreferrer' onClick={() => !item.url && item.description && setIsDescriptionVisible(true)} - className={`block text-left font-medium text-gray-900 ${item.description || item.url ? 'cursor-pointer hover:text-gray-600' : 'cursor-default'}`} + className={`block text-left font-medium text-gray-900 dark:text-gray-100 ${item.description || item.url ? 'cursor-pointer hover:text-gray-600 dark:hover:text-gray-400' : 'cursor-default'}`} > {item.done && ( diff --git a/pages/roadmap.tsx b/pages/roadmap.tsx index d5261bbeb4f8..359d7b642a30 100644 --- a/pages/roadmap.tsx +++ b/pages/roadmap.tsx @@ -2,11 +2,18 @@ import React from 'react'; import YouTubeEmbed from 'react-youtube-embed'; +import { ButtonIconPosition } from '@/types/components/buttons/ButtonPropsType'; import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; +import Button from '../components/buttons/Button'; +import IconCode from '../components/icons/Code'; +import IconGear from '../components/icons/Gear'; +import IconLightning from '../components/icons/Lightning'; +import IconPlus from '../components/icons/Plus'; import InlineHelp from '../components/InlineHelp'; import GenericLayout from '../components/layout/GenericLayout'; +import GoalCardRoadmapPage from '../components/roadmap/GoalCardRoadmapPage'; import RoadmapColumn from '../components/roadmap/RoadmapColumn'; import Heading from '../components/typography/Heading'; import Paragraph from '../components/typography/Paragraph'; @@ -492,136 +499,114 @@ export default function RoadmapPage() { return ( -
-
-
-
- - Vision - - - AsyncAPI becomes the #1 API specification for defining and developing APIs.{' '} - Any kind of APIs. - - - It all starts with a vision. This is where we want to see AsyncAPI by 2026. To make - this vision a reality, we've set some goals below that will help us to get there. - - - {/* eslint-disable-next-line react/no-unescaped-entities */} - These goals are not necessarily everything we'll have to do to get to that vision but instead, they are - manageable objectives we can already anticipate.{' '} - Together, vision and goals set the direction of the project. - +
+ {/* Hero Section */} +
+
+
+ + VISION 2026 + +

+ AsyncAPI: The #1 API Specification
for All APIs +

+
+ + Any kind of APIs + +
+

+ We envision AsyncAPI leading the way by 2026. To achieve this, we've set clear, actionable goals + to guide our journey. While not exhaustive, these objectives provide a roadmap for progress.{' '} + Vision and goals shape the project's future. +

+
+
-
-
-
-
- - Goal - - - AsyncAPI unifies all the API specifications - -
- - Nobody does only event-driven architectures. Most people complement them with REST (OpenAPI), - GraphQL, and/or RPC APIs.{' '} - - We want people to use the AsyncAPI specification and tooling together with their existing OpenAPI, - GraphQL, and gRPC definitions. - {' '} - For that purpose, our specification and tools will need to understand and leverage many other - specifications and tools. - - - This is not about reinventing the wheel or creating yet another spec to solve the same problems but - to integrate with the existing tools and specs instead. - -
-
-
- - Goal - - API development experience is seamless -
- - We want to make the development experience super seamless, from idea to production.{' '} - - Users should be able to create their first API within minutes without prior knowledge of AsyncAPI, - the communication protocol, or anything else that's not business logic. - - - - Furthermore, making sure production behavior and documentation are always aligned, independently of - the programming language or framework of choice. - -
-
-
- - Goal - - The AsyncAPI community grows 400% -
- - We are aware that our goals are ambitious.{' '} - - None of the other goals are possible if we don't have a huge community supporting us. AsyncAPI is - and must continue being a community-driven initiative. Now more than ever. - - - - We need people to contribute code, ideas, docs, articles, presentations, and more. The sponsors base - should also grow along with the community size and the donated money should serve to give back to - the community. - -
-
+ {/* Our Goals Section */} +
+
+

+ Our Goals +

+
+ + +
+
+
-
- - We'll be regularly setting outcomes or key results that will allow us to accomplish the goals above. You - can find a categorization of the outcomes in the roadmap below. They are split by priority and some of - them show the related features. - - - Please bear in mind this is an outcome-based roadmap. We don't maintain a long-term - release plan. If you want to find more specific and actionable items, head to the related repository - issues or start an issue/discussion at - - github.com/asyncapi/community - - . - + {/* Outcomes Section */} +
+
+
+

+ Outcomes we want to achieve +

+

+ At AsyncAPI, we focus on outcomes over rigid timelines. We prioritize meaningful results that drive our + goals forward. Our roadmap categorizes these outcomes by priority, highlighting related features. +

+
+ +
+ {/* Outcome-Based Approach */} +
+

+ Outcome-Based Approach +

+

+ We focus on impact, not rigid timelines. Instead of a long-term release plan, we prioritize meaningful results. +

+
- - Roadmap - - The outcomes we want to achieve + {/* Get Involved */} +
+

+ Get Involved ! +

+

+ Want to dive deeper? Check out our repository discussions or start an issue. +

+
+
+
+ + {/* Roadmap Graph Section */} +
+
+ + Roadmap + + The outcomes we want to achieve -
+
@@ -643,7 +628,7 @@ export default function RoadmapPage() {
-
+
Implementation{' '} @@ -672,7 +657,7 @@ export default function RoadmapPage() { />
- + If you want to know more about the format of this roadmap, watch this recording from one of our SIG meetings: From 3f7b4f88d1d0cf2783783819af10174ea863ef9a Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Thu, 6 Nov 2025 22:06:44 +0530 Subject: [PATCH 11/41] fix: adding events and updates page Signed-off-by: Shriya-Chauhan --- components/Calendar.tsx | 4 +- components/community/FeatureCard.tsx | 34 +++ .../community/UpcomingEventsSection.tsx | 184 +++++++++++++++ components/icons/Newsroom.tsx | 2 +- components/navigation/BlogPostItem.tsx | 30 +-- components/navigation/EventPostItem.tsx | 61 +++-- components/navigation/communityItems.tsx | 13 +- components/newsroom/NewsroomBlogPosts.tsx | 56 +---- components/newsroom/NewsroomSection.tsx | 2 +- components/newsroom/NewsroomYoutube.tsx | 59 +---- components/newsroom/YouTubeCard.tsx | 47 ++-- pages/community/events-and-updates.tsx | 217 ++++++++++++++++++ pages/community/index.tsx | 8 +- 13 files changed, 536 insertions(+), 181 deletions(-) create mode 100644 components/community/FeatureCard.tsx create mode 100644 components/community/UpcomingEventsSection.tsx create mode 100644 pages/community/events-and-updates.tsx diff --git a/components/Calendar.tsx b/components/Calendar.tsx index 5a5bfd2ba134..b438b0c6416e 100644 --- a/components/Calendar.tsx +++ b/components/Calendar.tsx @@ -34,11 +34,11 @@ export default function Calendar({ className = '', size }: ICalendarProps) { return (
- + {t('calendar.title')}
    diff --git a/components/community/FeatureCard.tsx b/components/community/FeatureCard.tsx new file mode 100644 index 000000000000..340d8ad158c4 --- /dev/null +++ b/components/community/FeatureCard.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; +import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; + +import Heading from '../typography/Heading'; +import Paragraph from '../typography/Paragraph'; + +interface FeatureCardProps { + icon: React.ReactNode; + title: string; + description: string; +} + +/** + * @description FeatureCard component for displaying a feature card with icon, title and description + * @param {FeatureCardProps} props - Props for the FeatureCard component + */ +export default function FeatureCard({ icon, title, description }: FeatureCardProps) { + return ( +
    +
    + {icon} +
    + + {title} + + + {description} + +
    + ); +} + diff --git a/components/community/UpcomingEventsSection.tsx b/components/community/UpcomingEventsSection.tsx new file mode 100644 index 000000000000..0debcd0a0fbc --- /dev/null +++ b/components/community/UpcomingEventsSection.tsx @@ -0,0 +1,184 @@ +import React, { useState } from 'react'; + +import EventFilter from '@/components/navigation/EventFilter'; +import EventPostItem from '@/components/navigation/EventPostItem'; +import Heading from '@/components/typography/Heading'; +import Paragraph from '@/components/typography/Paragraph'; +import meetings from '@/config/meetings.json'; +import type { Event } from '@/types/pages/community/Community'; +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; +import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; +import { getEvents } from '@/utils/staticHelpers'; + +/** + * @description UpcomingEventsSection component for displaying upcoming events with pagination + */ +export default function UpcomingEventsSection() { + const [events, setEvents] = useState(getEvents(meetings)); + const [currentPage, setCurrentPage] = useState(1); + const eventsPerPage = 6; + + // Calculate pagination + const indexOfLastEvent = currentPage * eventsPerPage; + const indexOfFirstEvent = indexOfLastEvent - eventsPerPage; + const currentEvents = events.slice(indexOfFirstEvent, indexOfLastEvent); + const totalPages = Math.ceil(events.length / eventsPerPage); + + const goToPage = (pageNumber: number) => { + setCurrentPage(pageNumber); + // Scroll to top of events section + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + const renderPageNumbers = () => { + const pageNumbers = []; + const maxVisiblePages = 5; + let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); + const endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); + + if (endPage - startPage + 1 < maxVisiblePages) { + startPage = Math.max(1, endPage - maxVisiblePages + 1); + } + + // Previous button + pageNumbers.push( + + ); + + // First page + if (startPage > 1) { + pageNumbers.push( + + ); + if (startPage > 2) { + pageNumbers.push( + + ... + + ); + } + } + + // Page numbers + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push( + + ); + } + + // Last page + if (endPage < totalPages) { + if (endPage < totalPages - 1) { + pageNumbers.push( + + ... + + ); + } + pageNumbers.push( + + ); + } + + // Next button + pageNumbers.push( + + ); + + return pageNumbers; + }; + + return ( +
    +
    + + Upcoming Events + + + Don't miss out on these amazing community gatherings + +
    + + {/* Events Grid */} +
    + {!currentEvents || currentEvents.length === 0 ? ( +
    + + No Events. Check back later! + +
    + ) : ( +
      + {currentEvents.map((event: Event, index: number) => { + return ; + })} +
    + )} +
    + + {/* Pagination */} + {totalPages > 1 && ( +
    +
    {renderPageNumbers()}
    +
    + Go to page + +
    +
    + )} +
    + ); +} + diff --git a/components/icons/Newsroom.tsx b/components/icons/Newsroom.tsx index e744b9d68081..04bbfe5d30a9 100644 --- a/components/icons/Newsroom.tsx +++ b/components/icons/Newsroom.tsx @@ -4,7 +4,7 @@ import React from 'react'; /** * @description Icons for asyncapi website */ -export default function IconNewsroom(className = '') { +export default function IconNewsroom({ className = '' }: { className?: string } = {}) { return ( {post.featured && ( -
    - +
    + Featured
    )} {post.title} -
    +
    {post.type} - + {post.title} - - + +
    -
    +
    -
    +
    - + {post.authors .map((author) => author.name) .join(', ') @@ -117,14 +117,14 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref: .join(' & ')} - + Data
    - - {post.readingTime} mins read + + {post.readingTime} min
    diff --git a/components/navigation/EventPostItem.tsx b/components/navigation/EventPostItem.tsx index c270e53861b7..4def19b5a20d 100644 --- a/components/navigation/EventPostItem.tsx +++ b/components/navigation/EventPostItem.tsx @@ -59,33 +59,48 @@ function EventPostItem({ post, className = '', id }: EventPostItemProps): React. return (
  • -
  • ); diff --git a/components/navigation/communityItems.tsx b/components/navigation/communityItems.tsx index fa6b3ee05965..fa8ee8a34710 100644 --- a/components/navigation/communityItems.tsx +++ b/components/navigation/communityItems.tsx @@ -4,7 +4,6 @@ import IconAmbassador from '../icons/Ambassador'; 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'; @@ -66,17 +65,11 @@ const communityItems: CommunityItem[] = [ description: 'Just need a good first issue to start your contribution journey? or want to see what topics are hot in discussion?' }, - { - icon: IconMeetings, - title: 'Events', - href: '/community/events', - description: 'See what events and meetings are organized under AsyncAPI umbrella and join one of them.' - }, { icon: IconNewsroom, - title: 'Newsroom', - href: '/community/newsroom', - description: 'Get upto date with the recent activity in the initiative.' + title: 'Events & Updates', + href: '/community/events-and-updates', + description: 'Stay updated with AsyncAPI events, blogs, videos, and community news.' } ]; diff --git a/components/newsroom/NewsroomBlogPosts.tsx b/components/newsroom/NewsroomBlogPosts.tsx index 7e79c1fb5342..5d6d4c52784d 100644 --- a/components/newsroom/NewsroomBlogPosts.tsx +++ b/components/newsroom/NewsroomBlogPosts.tsx @@ -1,21 +1,12 @@ -import React, { useState } from 'react'; -import { A11y, Navigation } from 'swiper/modules'; -import { Swiper, SwiperSlide } from 'swiper/react'; +import React from 'react'; import { getAllPosts } from '../../utils/api'; -import ArrowLeft from '../icons/ArrowLeft'; -import ArrowRight from '../icons/ArrowRight'; import BlogPostItem from '../navigation/BlogPostItem'; -import { checkLastSnapIndex, useSwiperRef } from './swiper'; /** * @description This component displays the latest blog posts. */ export default function NewsroomBlogPosts() { - const [nextEl, nextElRef] = useSwiperRef(); - const [prevEl, prevElRef] = useSwiperRef(); - const [current, setCurrent] = useState(0); - const posts = getAllPosts() .blog.sort((i1, i2) => { const i1Date = new Date(i1.date); @@ -26,50 +17,15 @@ export default function NewsroomBlogPosts() { return i2Date.valueOf() - i1Date.valueOf(); }) - .slice(0, 5); - - const buttonClass = 'shadow-md rounded border mx-2 mb-2 focus:outline-none'; + .slice(0, 3); return ( -
    - setCurrent(swiper.snapIndex)} - navigation={{ - prevEl, - nextEl - }} - breakpoints={{ - 640: { - slidesPerView: 2 - } - }} - > +
    +
      {posts.map((post, index) => ( - - - + ))} - - -
      - - -
      +
    ); } diff --git a/components/newsroom/NewsroomSection.tsx b/components/newsroom/NewsroomSection.tsx index 7e660e69dfe4..0ecd482c6976 100644 --- a/components/newsroom/NewsroomSection.tsx +++ b/components/newsroom/NewsroomSection.tsx @@ -56,7 +56,7 @@ export default function NewsroomSection() {
    diff --git a/components/newsroom/NewsroomYoutube.tsx b/components/newsroom/NewsroomYoutube.tsx index ced5fceedbe2..723bafe59a99 100644 --- a/components/newsroom/NewsroomYoutube.tsx +++ b/components/newsroom/NewsroomYoutube.tsx @@ -1,11 +1,6 @@ -import React, { useState } from 'react'; -import { A11y, Navigation } from 'swiper/modules'; -import { Swiper, SwiperSlide } from 'swiper/react'; +import React from 'react'; import videoData from '../../config/newsroom_videos.json'; -import ArrowLeft from '../icons/ArrowLeft'; -import ArrowRight from '../icons/ArrowRight'; -import { checkLastSnapIndex, useSwiperRef } from './swiper'; import YouTubeCard from './YouTubeCard'; interface NewsroomYoutubeProps { @@ -13,57 +8,21 @@ interface NewsroomYoutubeProps { } /** - * Newsroom Youtube component displays a Swiper carousel of YouTubeCard components. + * Newsroom Youtube component displays a grid of YouTubeCard components. * @description This component displays the latest videos from the AsyncAPI YouTube channel. * @param {string} [props.className=''] - Additional CSS classes for styling. */ export default function NewsroomYoutube({ className = '' }: NewsroomYoutubeProps) { - const [nextEl, nextElRef] = useSwiperRef(); - const [prevEl, prevElRef] = useSwiperRef(); - const [current, setCurrent] = useState(0); - - const buttonClass = 'shadow-md rounded border mx-2 mb-2 focus:outline-none'; + // Only show the first 3 videos + const videos = videoData.slice(0, 3); return ( -
    - setCurrent(swiper.snapIndex)} - navigation={{ - prevEl, - nextEl - }} - breakpoints={{ - 640: { - slidesPerView: 2 - } - }} - > - {videoData.map((video, index) => ( - - - +
    +
      + {videos.map((video, index) => ( + ))} - - -
      - - -
      +
    ); } diff --git a/components/newsroom/YouTubeCard.tsx b/components/newsroom/YouTubeCard.tsx index f7d9ffa2b1b6..949f205d16df 100644 --- a/components/newsroom/YouTubeCard.tsx +++ b/components/newsroom/YouTubeCard.tsx @@ -6,7 +6,6 @@ import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; import ArrowRight from '../icons/ArrowRight'; import Heading from '../typography/Heading'; import Paragraph from '../typography/Paragraph'; -import TextLink from '../typography/TextLink'; interface YouTubeVideo { image_url: string; @@ -25,34 +24,32 @@ interface YouTubeCardProps { */ export default function YouTubeCard({ video }: YouTubeCardProps) { return ( -
  • - +
  • ); } diff --git a/pages/community/events-and-updates.tsx b/pages/community/events-and-updates.tsx new file mode 100644 index 000000000000..92ed259d80c5 --- /dev/null +++ b/pages/community/events-and-updates.tsx @@ -0,0 +1,217 @@ +import React from 'react'; + +import Button from '@/components/buttons/Button'; +import GoogleCalendarButton from '@/components/buttons/GoogleCalendarButton'; +import ICSFileButton from '@/components/buttons/ICSFileButton'; +import SubscribeButton from '@/components/buttons/SubscribeButton'; +import Calendar from '@/components/Calendar'; +import FeatureCard from '@/components/community/FeatureCard'; +import IconCalendar from '@/components/icons/Calendar'; +import IconNewsroom from '@/components/icons/Newsroom'; +import IconUsersGroup from '@/components/icons/UsersGroup'; +import IconVideo from '@/components/icons/Video'; +import Container from '@/components/layout/Container'; +import GenericLayout from '@/components/layout/GenericLayout'; +import NewsletterSubscribe from '@/components/NewsletterSubscribe'; +import NewsroomBlogPosts from '@/components/newsroom/NewsroomBlogPosts'; +import NewsroomYoutube from '@/components/newsroom/NewsroomYoutube'; +import Heading from '@/components/typography/Heading'; +import Paragraph from '@/components/typography/Paragraph'; +import TextLink from '@/components/typography/TextLink'; +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; +import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; +import { makeStaticProps } from '@/utils/getStatic'; + +const getStaticProps = makeStaticProps(['landing-page', 'footer', 'common']); + +export { getStaticProps }; + +/** + * @description Events & Updates page combining both newsroom and events functionality + */ +export default function EventsAndUpdates() { + const description = 'Stay updated with AsyncAPI events, blogs, videos, and community news'; + const image = '/img/social/community-events.webp'; + + return ( + + {/* Hero Section */} +
    + {/* Decorative gradient orbs */} +
    +
    +
    +
    + + +
    +
    + Find out what's New +
    + + Events & Updates + + + Join thousands of developers, creators, and innovators building the future of event-driven architectures. + Your journey starts here—connect, learn, and grow with us. + +
    +
    +
    +
    +
    + + {/* Everything You Need Section */} +
    + +
    + + Everything You Need + + + Discover, connect, and grow with our comprehensive community platform + +
    +
    + } + title='Live Events' + description='Join workshops, meetups, and conferences from anywhere in the world' + /> + } + title='Latest Updates' + description='Stay informed with news, releases, and community highlights' + /> + } + title='Video Library' + description='Access recordings, tutorials, and live streams on demand' + /> + } + title='Global Community' + description='Connect with developers and contributors worldwide' + /> +
    +
    +
    + + {/* Upcoming Events Section */} +
    + +
    + + Upcoming Events + + + Don't miss out on these amazing community gatherings + +
    + +
    +
    + +
    +
    +
    + + Join AsyncAPI meetings + + + We meet every week to discuss the future of AsyncAPI and event-driven architectures. Join us to share + your ideas, ask questions, and connect with the community.{' '} + + Learn more about our meetings + + +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    + + {/* From the Blogs Section */} +
    + +
    +
    + + From the Blogs + + + Stay connected with the pulse of our community + +
    +
    +
    + +
    +
    +
    + + {/* Videos & Live Streams Section */} +
    + +
    +
    + + Videos & Live Streams + + + Learn, engage, and stay updated with our video content + +
    +
    +
    + +
    +
    +
    + + {/* Newsletter Section */} +
    + + + +
    + + ); +} diff --git a/pages/community/index.tsx b/pages/community/index.tsx index c9334dc5b319..ad72de8c9b00 100644 --- a/pages/community/index.tsx +++ b/pages/community/index.tsx @@ -87,12 +87,12 @@ export default function CommunityIndexPage() {
    @@ -118,7 +118,7 @@ export default function CommunityIndexPage() { events.' className='bg-eventCover' btnText='Explore more events' - link='/community/events' + link='/community/events-and-updates' />
      From 2ed5445a87bff59c359ff9378517a99d418c12b1 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Thu, 6 Nov 2025 23:01:49 +0530 Subject: [PATCH 12/41] fix: lint errors --- components/Calendar.tsx | 6 +- components/community/FeatureCard.tsx | 1 - components/community/TSCMemberCard.tsx | 17 ++- .../community/UpcomingEventsSection.tsx | 11 +- components/icons/Book.tsx | 14 +-- components/icons/Clipboard.tsx | 3 + components/icons/CodeBrackets.tsx | 6 +- components/icons/Document.tsx | 6 +- components/icons/Gear.tsx | 11 +- components/icons/LightBulb.tsx | 5 +- components/icons/Lightning.tsx | 4 +- components/icons/Microphone.tsx | 6 +- components/icons/Play.tsx | 6 +- components/icons/Plus.tsx | 4 +- components/icons/Star.tsx | 6 +- components/icons/Terminal.tsx | 6 +- components/icons/UsersGroup.tsx | 6 +- components/icons/Video.tsx | 6 +- components/navigation/BlogPostItem.tsx | 18 +++- components/navigation/EventPostItem.tsx | 18 +--- components/newsroom/YouTubeCard.tsx | 11 +- components/roadmap/GoalCardRoadmapPage.tsx | 1 - components/roadmap/RoadmapItem.tsx | 7 +- pages/blog/index.tsx | 48 +++++---- pages/community/ambassadors/index.tsx | 101 ++++++++++-------- pages/community/events-and-updates.tsx | 27 +++-- pages/community/tsc.tsx | 64 +++++------ pages/roadmap.tsx | 27 ++--- 28 files changed, 248 insertions(+), 198 deletions(-) diff --git a/components/Calendar.tsx b/components/Calendar.tsx index b438b0c6416e..67f152afce92 100644 --- a/components/Calendar.tsx +++ b/components/Calendar.tsx @@ -38,7 +38,11 @@ export default function Calendar({ className = '', size }: ICalendarProps) { className )} > - + {t('calendar.title')}
        diff --git a/components/community/FeatureCard.tsx b/components/community/FeatureCard.tsx index 340d8ad158c4..d15985ce842f 100644 --- a/components/community/FeatureCard.tsx +++ b/components/community/FeatureCard.tsx @@ -31,4 +31,3 @@ export default function FeatureCard({ icon, title, description }: FeatureCardPro
    ); } - diff --git a/components/community/TSCMemberCard.tsx b/components/community/TSCMemberCard.tsx index 0022023c2f5c..362405fb9d8a 100644 --- a/components/community/TSCMemberCard.tsx +++ b/components/community/TSCMemberCard.tsx @@ -16,15 +16,9 @@ export default function TSCMemberCard({ member }: TSCMemberCardProps) { className='bg-white dark:bg-dark-card rounded-xl border border-gray-200 dark:border-gray-700 p-6 hover:shadow-lg transition-shadow' >
    - {member.name} + {member.name}
    -

    - {member.name} -

    +

    {member.name}

    {member.availableForHire ? 'Available for hire' : member.company || 'Individual Member'}

    @@ -66,7 +60,11 @@ export default function TSCMemberCard({ member }: TSCMemberCardProps) { className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' > - + )} @@ -98,4 +96,3 @@ export default function TSCMemberCard({ member }: TSCMemberCardProps) {
    ); } - diff --git a/components/community/UpcomingEventsSection.tsx b/components/community/UpcomingEventsSection.tsx index 0debcd0a0fbc..00787d462f8e 100644 --- a/components/community/UpcomingEventsSection.tsx +++ b/components/community/UpcomingEventsSection.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; -import EventFilter from '@/components/navigation/EventFilter'; import EventPostItem from '@/components/navigation/EventPostItem'; import Heading from '@/components/typography/Heading'; import Paragraph from '@/components/typography/Paragraph'; @@ -14,7 +13,7 @@ import { getEvents } from '@/utils/staticHelpers'; * @description UpcomingEventsSection component for displaying upcoming events with pagination */ export default function UpcomingEventsSection() { - const [events, setEvents] = useState(getEvents(meetings)); + const [events] = useState(getEvents(meetings)); const [currentPage, setCurrentPage] = useState(1); const eventsPerPage = 6; @@ -136,7 +135,7 @@ export default function UpcomingEventsSection() { Upcoming Events - Don't miss out on these amazing community gatherings + Don't miss out on these amazing community gatherings
    @@ -159,7 +158,10 @@ export default function UpcomingEventsSection() { {/* Pagination */} {totalPages > 1 && ( -
    +
    {renderPageNumbers()}
    Go to page @@ -181,4 +183,3 @@ export default function UpcomingEventsSection() {
    ); } - diff --git a/components/icons/Book.tsx b/components/icons/Book.tsx index 05f83c3b448b..2b4e7adf58d4 100644 --- a/components/icons/Book.tsx +++ b/components/icons/Book.tsx @@ -1,12 +1,9 @@ +/** + * @description Icon component for Book + */ export default function IconBook({ className = '' }) { return ( - + ); } - - - diff --git a/components/icons/Clipboard.tsx b/components/icons/Clipboard.tsx index 84f32909d98c..4d7d266d0807 100644 --- a/components/icons/Clipboard.tsx +++ b/components/icons/Clipboard.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Clipboard + */ export default function IconClipboard({ className = '' }) { return ( diff --git a/components/icons/CodeBrackets.tsx b/components/icons/CodeBrackets.tsx index 13bd49c2f3a3..2562a8efece9 100644 --- a/components/icons/CodeBrackets.tsx +++ b/components/icons/CodeBrackets.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Code Brackets + */ export default function IconCodeBrackets({ className = '' }) { return ( @@ -9,6 +12,3 @@ export default function IconCodeBrackets({ className = '' }) { ); } - - - diff --git a/components/icons/Document.tsx b/components/icons/Document.tsx index 595f7894240f..1aa995d6ec7d 100644 --- a/components/icons/Document.tsx +++ b/components/icons/Document.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Document + */ export default function IconDocument({ className = '' }) { return ( @@ -9,6 +12,3 @@ export default function IconDocument({ className = '' }) { ); } - - - diff --git a/components/icons/Gear.tsx b/components/icons/Gear.tsx index 0cc43603d53e..4bafcc6abd12 100644 --- a/components/icons/Gear.tsx +++ b/components/icons/Gear.tsx @@ -1,9 +1,16 @@ +/** + * @description Icon component for Gear + */ export default function IconGear({ className = '' }) { return ( - + ); } - diff --git a/components/icons/LightBulb.tsx b/components/icons/LightBulb.tsx index 97fe3f25ecde..78829c643593 100644 --- a/components/icons/LightBulb.tsx +++ b/components/icons/LightBulb.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Light Bulb + */ export default function IconLightbulb({ className = '' }) { return ( @@ -5,5 +8,3 @@ export default function IconLightbulb({ className = '' }) { ); } - - diff --git a/components/icons/Lightning.tsx b/components/icons/Lightning.tsx index 02400d7a5a6f..af9809b44402 100644 --- a/components/icons/Lightning.tsx +++ b/components/icons/Lightning.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Lightning + */ export default function IconLightning({ className = '' }) { return ( @@ -5,4 +8,3 @@ export default function IconLightning({ className = '' }) { ); } - diff --git a/components/icons/Microphone.tsx b/components/icons/Microphone.tsx index f3756ca7ba3c..b50c88a15fa7 100644 --- a/components/icons/Microphone.tsx +++ b/components/icons/Microphone.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Microphone + */ export default function IconMicrophone({ className = '' }) { return ( @@ -9,6 +12,3 @@ export default function IconMicrophone({ className = '' }) { ); } - - - diff --git a/components/icons/Play.tsx b/components/icons/Play.tsx index 7551c192a1db..d552a8dd691b 100644 --- a/components/icons/Play.tsx +++ b/components/icons/Play.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Play + */ export default function IconPlay({ className = '' }) { return ( @@ -5,6 +8,3 @@ export default function IconPlay({ className = '' }) { ); } - - - diff --git a/components/icons/Plus.tsx b/components/icons/Plus.tsx index 2d989a9ed1ad..a2216e94cd84 100644 --- a/components/icons/Plus.tsx +++ b/components/icons/Plus.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Plus + */ export default function IconPlus({ className = '' }) { return ( @@ -5,4 +8,3 @@ export default function IconPlus({ className = '' }) { ); } - diff --git a/components/icons/Star.tsx b/components/icons/Star.tsx index 0b701fdbc0a1..49423c8fe4fe 100644 --- a/components/icons/Star.tsx +++ b/components/icons/Star.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Star + */ export default function IconStar({ className = '' }) { return ( @@ -5,6 +8,3 @@ export default function IconStar({ className = '' }) { ); } - - - diff --git a/components/icons/Terminal.tsx b/components/icons/Terminal.tsx index eb8a4c6da366..3e478b49a07f 100644 --- a/components/icons/Terminal.tsx +++ b/components/icons/Terminal.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Terminal + */ export default function IconTerminal({ className = '' }) { return ( @@ -9,6 +12,3 @@ export default function IconTerminal({ className = '' }) { ); } - - - diff --git a/components/icons/UsersGroup.tsx b/components/icons/UsersGroup.tsx index 73a04aa9056d..4eb15ae6ff11 100644 --- a/components/icons/UsersGroup.tsx +++ b/components/icons/UsersGroup.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Users Group + */ export default function IconUsersGroup({ className = '' }) { return ( @@ -5,6 +8,3 @@ export default function IconUsersGroup({ className = '' }) { ); } - - - diff --git a/components/icons/Video.tsx b/components/icons/Video.tsx index 564d0b59bc18..fb546066f318 100644 --- a/components/icons/Video.tsx +++ b/components/icons/Video.tsx @@ -1,3 +1,6 @@ +/** + * @description Icon component for Video + */ export default function IconVideo({ className = '' }) { return ( @@ -5,6 +8,3 @@ export default function IconVideo({ className = '' }) { ); } - - - diff --git a/components/navigation/BlogPostItem.tsx b/components/navigation/BlogPostItem.tsx index 2c9c8f2ba692..60178771e26c 100644 --- a/components/navigation/BlogPostItem.tsx +++ b/components/navigation/BlogPostItem.tsx @@ -1,4 +1,3 @@ -import moment from 'moment'; import Link from 'next/link'; import type { Ref } from 'react'; import React, { forwardRef } from 'react'; @@ -94,10 +93,17 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref: {post.type} - + {post.title} - +
    @@ -107,7 +113,11 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref:
    - + {post.authors .map((author) => author.name) diff --git a/components/navigation/EventPostItem.tsx b/components/navigation/EventPostItem.tsx index 4def19b5a20d..0422f2886d18 100644 --- a/components/navigation/EventPostItem.tsx +++ b/components/navigation/EventPostItem.tsx @@ -1,14 +1,9 @@ -import { ArrowRightIcon } from '@heroicons/react/outline'; import moment from 'moment'; import React from 'react'; import type { IEvent } from '@/types/event'; import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; -import IconCalendar from '../icons/Calendar'; -import Community from '../icons/Community'; -import Conference from '../icons/Conference'; -import Webinar from '../icons/Webinar'; import Heading from '../typography/Heading'; interface EventPostItemProps { @@ -28,25 +23,16 @@ function EventPostItem({ post, className = '', id }: EventPostItemProps): React. const localTime = moment().format('YYYY-MM-DD'); // store localTime const currentDate = `${localTime}T00:00:00.000Z`; const title = post.title || ''; - let color = ''; - let icon: React.ReactElement | null = null; let type = ''; if (title.includes('community')) { - icon = ; - color = 'text-green-800'; type = 'COMMUNITY'; } else if (title.includes('conference')) { - icon = ; - color = 'text-orange-800'; type = 'CONFERENCE'; } else if (title.includes('workshop')) { - icon = ; - color = 'text-blue-400'; type = 'WORKSHOP'; } - const defaultCover = 'https://github.com/asyncapi/community/assets/40604284/01c2b8de-fa5c-44dd-81a5-70cb96df4813'; let active = true; const postDate = moment(post.date); // Convert post.date to a moment object if necessary @@ -85,9 +71,7 @@ function EventPostItem({ post, className = '', id }: EventPostItemProps): React. {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'}
    -
    - {postDate.isValid() && moment(postDate).format('h:mm A [UTC]')} -
    +
    {postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}
    {/* Button */} diff --git a/components/newsroom/YouTubeCard.tsx b/components/newsroom/YouTubeCard.tsx index 949f205d16df..4de71df4187d 100644 --- a/components/newsroom/YouTubeCard.tsx +++ b/components/newsroom/YouTubeCard.tsx @@ -32,10 +32,17 @@ export default function YouTubeCard({ video }: YouTubeCardProps) {
    - + {video.title} - + {video.description}
    diff --git a/components/roadmap/GoalCardRoadmapPage.tsx b/components/roadmap/GoalCardRoadmapPage.tsx index 31491bbf3661..a8afe478544c 100644 --- a/components/roadmap/GoalCardRoadmapPage.tsx +++ b/components/roadmap/GoalCardRoadmapPage.tsx @@ -23,4 +23,3 @@ export default function GoalCardRoadmapPage({ icon: Icon, title, description }:
    ); } - diff --git a/components/roadmap/RoadmapItem.tsx b/components/roadmap/RoadmapItem.tsx index 903635c5f195..07d9293097b7 100644 --- a/components/roadmap/RoadmapItem.tsx +++ b/components/roadmap/RoadmapItem.tsx @@ -59,7 +59,12 @@ export default function RoadmapItem({ item, colorClass, showConnector = true, co )} {!isCollapsed && item?.implementations?.length && ( - + )} ); diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx index 37185eb52231..a11c6105e7d4 100644 --- a/pages/blog/index.tsx +++ b/pages/blog/index.tsx @@ -71,7 +71,7 @@ export default function BlogIndexPage() { useEffect(() => { setIsClient(true); - + // Set posts per page based on screen size const handleResize = () => { if (window.innerWidth < 640) { @@ -80,13 +80,13 @@ export default function BlogIndexPage() { setPostsPerPage(9); // Tablet/Desktop: 9 posts } }; - + // Set initial value handleResize(); - + // Add event listener window.addEventListener('resize', handleResize); - + // Cleanup return () => window.removeEventListener('resize', handleResize); }, []); @@ -98,6 +98,7 @@ export default function BlogIndexPage() { if (hasDropdownFilters) return true; // Otherwise, apply tab filter if (activeTab === 'All Posts') return true; + return post.type.toLowerCase() === activeTab.toLowerCase(); }); @@ -126,15 +127,25 @@ export default function BlogIndexPage() {
    - + Welcome to our blog! Find the latest and greatest stories from our community - + Stay in touch! Get blog posts delivered directly to your{' '} - + email . @@ -164,17 +175,20 @@ export default function BlogIndexPage() { {/* Category Tabs - Hidden on mobile */}
    -
    +
    {tabs.map((tab) => ( @@ -237,9 +251,7 @@ export default function BlogIndexPage() { {Array.from({ length: totalPages }, (_, i) => i + 1) .filter((page) => { return ( - page === 1 || - page === totalPages || - (page >= currentPage - 1 && page <= currentPage + 1) + page === 1 || page === totalPages || (page >= currentPage - 1 && page <= currentPage + 1) ); }) .map((page, index, array) => { @@ -284,9 +296,7 @@ export default function BlogIndexPage() { › - - Go to page - + Go to page setSearchName(e.target.value)} /> {searchName && ( - )}
    {isFiltered && ( -
    +
    - Clear Filters + Clear Filters
    )}
    @@ -239,8 +239,8 @@ export default function ToolsDashboard() { ) : (
    - not found -
    Sorry, we don't have tools according to your needs.
    + not found +
    Sorry, we don't have tools according to your needs.
    )}
    diff --git a/components/tools/ToolsList.tsx b/components/tools/ToolsList.tsx index 5ce95974d722..60f8c25406d3 100644 --- a/components/tools/ToolsList.tsx +++ b/components/tools/ToolsList.tsx @@ -24,12 +24,14 @@ export default function ToolsList({ toolsListData }: ToolsListProp) { {Object.keys(toolsListData).map((categoryName, index) => { if (toolsListData[categoryName].toolsList.length > 0) { return ( -
    - - {categoryName} - - {toolsListData[categoryName].description} -
    +
    +
    + + {categoryName} + + {toolsListData[categoryName].description} +
    +
    {toolsListData[categoryName].toolsList.map((tool, toolIndex) => ( diff --git a/pages/tools/index.tsx b/pages/tools/index.tsx index f9c64927064c..8893da30e0b7 100644 --- a/pages/tools/index.tsx +++ b/pages/tools/index.tsx @@ -17,26 +17,98 @@ export default function ToolsIndex() { const image = '/img/social/tools-dashboard-card.webp'; return ( -
    +
    -
    -
    - - AsyncAPI Tools Dashboard - - - Discover various AsyncAPI tools to optimize your journey! These tools are made by the community, for the - community. Have an AsyncAPI tool you want to be featured on this list? Then follow the procedure given in - the - - Tool Documentation - {' '} - file, and show up your AsyncAPI Tool card in the website. - +
    + {/* Hero Section */} +
    + {/* Background decorative elements */} +
    + {/* Gradient orbs */} +
    +
    + + {/* Grid pattern */} +
    +
    + + {/* Content */} +
    + {/* Icon/Badge */} +
    + + + + Community Tools +
    + +
    + + AsyncAPI Tools Dashboard + +
    +
    + + + Discover various AsyncAPI tools to optimize your journey! These tools are made by the community, for the + community. Have an AsyncAPI tool you want to be featured on this list? Then follow the procedure given + in the{' '} + + Tool Documentation + {' '} + file, and show up your AsyncAPI Tool card in the website. + + + {/* Stats or Feature badges */} +
    +
    + + + + Open Source +
    +
    + + + + Community Driven +
    +
    + + + + Production Ready +
    +
    +
    + From e1a3b59f4d3a4cf0662f79e7327e7705eb055da6 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Sun, 9 Nov 2025 14:19:24 +0530 Subject: [PATCH 16/41] fix:enhanced blog page card animation Signed-off-by: Shriya-Chauhan --- components/navigation/BlogPostItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/navigation/BlogPostItem.tsx b/components/navigation/BlogPostItem.tsx index 60178771e26c..31cff9ee1786 100644 --- a/components/navigation/BlogPostItem.tsx +++ b/components/navigation/BlogPostItem.tsx @@ -67,7 +67,7 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref:
    {post.featured && ( From f3830eed36746c5043412dbdc65a87ebae7d99d8 Mon Sep 17 00:00:00 2001 From: Shriya-Chauhan Date: Mon, 10 Nov 2025 23:57:01 +0530 Subject: [PATCH 17/41] fix: community page Signed-off-by: Shriya-Chauhan --- components/community/Card.tsx | 18 +- components/community/FeatureCard.tsx | 4 +- components/community/Header.tsx | 4 +- components/community/HomeCard.tsx | 8 +- components/community/ToolingCard.tsx | 97 ++++ components/community/ToolingsShowcase.tsx | 181 ++++++++ .../community/UpcomingEventsSection.tsx | 24 +- pages/community/index.tsx | 432 +++++++++++------- public/img/social/community-3.webp | Bin 0 -> 65922 bytes public/img/social/communitypage.webp | Bin 0 -> 55622 bytes public/img/social/slack-ss.webp | Bin 0 -> 57516 bytes public/img/social/woman.jpg | Bin 0 -> 68804 bytes styles/globals.css | 9 + 13 files changed, 578 insertions(+), 199 deletions(-) create mode 100644 components/community/ToolingCard.tsx create mode 100644 components/community/ToolingsShowcase.tsx create mode 100644 public/img/social/community-3.webp create mode 100644 public/img/social/communitypage.webp create mode 100644 public/img/social/slack-ss.webp create mode 100644 public/img/social/woman.jpg diff --git a/components/community/Card.tsx b/components/community/Card.tsx index 00f4f0eb9ae8..af3249920c2d 100644 --- a/components/community/Card.tsx +++ b/components/community/Card.tsx @@ -51,23 +51,23 @@ export default function Card({ return (
    -
    +
    {icon} {tagline}
    - + {heading}
    - + {description}
    -
    +
    @@ -77,23 +77,23 @@ export default function Card({ return (
    {icon} {tagline}
    - + {heading}
    - {description} + {description}
    diff --git a/components/community/FeatureCard.tsx b/components/community/FeatureCard.tsx index d15985ce842f..2e1663c6e7a5 100644 --- a/components/community/FeatureCard.tsx +++ b/components/community/FeatureCard.tsx @@ -19,10 +19,10 @@ interface FeatureCardProps { export default function FeatureCard({ icon, title, description }: FeatureCardProps) { return (
    -
    +
    {icon}
    - + {title} diff --git a/components/community/Header.tsx b/components/community/Header.tsx index f531ce17f174..2aeb159da97d 100644 --- a/components/community/Header.tsx +++ b/components/community/Header.tsx @@ -25,7 +25,7 @@ export default function Header({ className = '' }: HeaderProps) { AsyncAPI Community
    - + Welcome to the
    AsyncAPI Community @@ -37,7 +37,7 @@ export default function Header({ className = '' }: HeaderProps) { level={HeadingLevel.h2} typeStyle={HeadingTypeStyle.bodyMd} textColor='text-gray-700' - className='text-slate-500' + className='text-slate-500 dark:text-gray-300' > We're an OSS community that's passionate about AsyncAPI. Join us in building the future of Event Driven APIs by asking questions, sharing ideas, and building connections. diff --git a/components/community/HomeCard.tsx b/components/community/HomeCard.tsx index de34b46cf42a..96dada2d5a6c 100644 --- a/components/community/HomeCard.tsx +++ b/components/community/HomeCard.tsx @@ -27,22 +27,22 @@ interface HomeCardProps { */ export default function HomeCards({ headline, title, description, btnText, link, className }: HomeCardProps) { return ( -
    +
    - + {headline}
    - + {title} {description} diff --git a/components/community/ToolingCard.tsx b/components/community/ToolingCard.tsx new file mode 100644 index 000000000000..38800cd3ff90 --- /dev/null +++ b/components/community/ToolingCard.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +interface ToolingCardProps { + name: string; + description: string; + badge?: string; + language: string; + languageColor?: string; + link?: string; + isShuffling?: boolean; +} + +/** + * @description ToolingCard component for displaying AsyncAPI tools with stacked, rotated effect + */ +export default function ToolingCard({ name, description, badge, language, languageColor, link, isShuffling }: ToolingCardProps) { + const CardContent = ( +
    + {/* Stacked Cards Behind - Positioned on LEFT side */} +
    +
    + + {/* Main Card */} +
    + {/* Background Vector Graphics - Matching exact design */} +
    + {/* Top Background Layers - Gray to Purple gradient triangular shapes */} +
    +
    +
    + + {/* Bottom Large Fan/Petal Shape - Blue gradient */} +
    + + + + + + + + + {/* Fan/Petal shape spreading from bottom left */} + + +
    +
    + + {/* Content */} +
    + {/* Badge */} + {badge && ( +
    + {badge} +
    + )} + + {/* Tool Name */} +

    {name}

    + + {/* Description */} +

    {description}

    + + {/* Language Tag */} +
    + {language} +
    +
    +
    +
    + ); + + if (link) { + return ( + + {CardContent} + + ); + } + + return CardContent; +} + diff --git a/components/community/ToolingsShowcase.tsx b/components/community/ToolingsShowcase.tsx new file mode 100644 index 000000000000..3ebb5de6556e --- /dev/null +++ b/components/community/ToolingsShowcase.tsx @@ -0,0 +1,181 @@ +'use client'; + +import React, { useState } from 'react'; + +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; +import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; + +import Heading from '../typography/Heading'; +import Paragraph from '../typography/Paragraph'; +import ToolingCard from './ToolingCard'; + +interface ToolData { + name: string; + displayName: string; + description: string; + language: string; + link: string; + color: string; + badge?: string; +} + +const toolsData: Record = { + 'go-asyncapi': { + name: 'go-asyncapi', + displayName: 'Go AsyncAPI', + description: + 'This library helps to create AsyncAPI spec from your Go message structures. It uses reflection to translate Go structures in JSON Schema definitions and arrange them in AsyncAPI schema.', + language: 'Go', + link: 'https://github.com/swaggest/go-asyncapi', + color: 'border-cyan-400 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-50 dark:hover:bg-cyan-900/20', + badge: 'Open Source' + }, + faststream: { + name: 'faststream', + displayName: 'FastStream', + description: + 'A powerful and easy-to-use Python framework for building asynchronous services interacting with event streams such as Apache Kafka, RabbitMQ and NATS.', + language: 'Python', + link: 'https://faststream.ag2.ai', + color: 'border-cyan-400 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-50 dark:hover:bg-cyan-900/20', + badge: 'Open Source' + }, + modelina: { + name: 'modelina', + displayName: 'AsyncAPI Modelina', + description: + 'Generate payload models into Java, TypeScript, Go, etc, you name it, from AsyncAPI documents. This tool gives you full control over the models through high customization.', + language: 'TypeScript', + link: 'https://modelina.org', + color: 'border-cyan-400 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-50 dark:hover:bg-cyan-900/20', + badge: 'Open Source' + }, + converter: { + name: 'converter', + displayName: 'Converter', + description: 'Converts old versions of AsyncAPI files into the latest version.', + language: 'TypeScript', + link: 'https://www.asyncapi.com/tools/converter', + color: 'border-green-400 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/20', + badge: 'Open Source' + }, + 'java-asyncapi': { + name: 'java-asyncapi', + displayName: 'Java AsyncAPI', + description: 'This tool stores modules, which simplifies interacting with AsyncAPI in jvm ecosystem.', + language: 'Java', + link: 'https://github.com/asyncapi/jasyncapi', + color: 'border-cyan-400 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-50 dark:hover:bg-cyan-900/20', + badge: 'Open Source' + }, + 'kotlin-asyncapi': { + name: 'kotlin-asyncapi', + displayName: 'Kotlin AsyncAPI', + description: + 'The Kotlin AsyncAPI project aims to provide convenience tools for generating and serving AsyncAPI documentation. The core of this project is a Kotlin DSL for building the specification in a typesafe way.', + language: 'Kotlin', + link: 'https://github.com/OpenFolder/kotlin-asyncapi', + color: 'border-cyan-400 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-50 dark:hover:bg-cyan-900/20', + badge: 'Open Source' + }, + saunter: { + name: 'saunter', + displayName: 'Saunter', + description: + 'Saunter is an AsyncAPI documentation generator for dotnet. Generates (and hosts) an AsyncAPI schema document from your code.', + language: 'C#', + link: 'https://github.com/tehmantra/saunter', + color: 'border-pink-500 text-pink-600 dark:text-pink-400 hover:bg-pink-50 dark:hover:bg-pink-900/20', + badge: 'Open Source' + } +}; + +/** + * @description ToolingsShowcase component for displaying AsyncAPI tools collection + */ +export default function ToolingsShowcase() { + const [selectedTool, setSelectedTool] = useState('java-asyncapi'); + const [isShuffling, setIsShuffling] = useState(false); + + const currentTool = toolsData[selectedTool]; + + const handleToolChange = (toolName: string) => { + if (toolName === selectedTool) return; + + // Start shuffle animation + setIsShuffling(true); + + // Change tool after shuffle animation starts + setTimeout(() => { + setSelectedTool(toolName); + setIsShuffling(false); + }, 300); + }; + + return ( +
    +
    +
    + {/* Left Content */} +
    +
    + + Checkout Our Collection of Toolings + + + Discover various AsyncAPI tools to optimize your journey! These tools are made by the community, for the + community. + +
    + + {/* Explore Tools Button */} + + Explore Tools + + + {/* Tool Tags */} +
    + {Object.values(toolsData).map((tool) => ( + + ))} +
    +
    + + {/* Right Card Showcase */} +
    + +
    +
    +
    +
    + ); +} + diff --git a/components/community/UpcomingEventsSection.tsx b/components/community/UpcomingEventsSection.tsx index 00787d462f8e..f5503094fd09 100644 --- a/components/community/UpcomingEventsSection.tsx +++ b/components/community/UpcomingEventsSection.tsx @@ -45,7 +45,7 @@ export default function UpcomingEventsSection() { key='prev' onClick={() => goToPage(currentPage - 1)} disabled={currentPage === 1} - className='rounded border border-gray-300 bg-white px-3 py-2 text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50' + className='rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-dark-card px-3 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-50' data-testid='Pagination-Prev' > < @@ -58,7 +58,7 @@ export default function UpcomingEventsSection() {